From 6ee3fea0ce128b2ebdeae88e89a283fd211150f6 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:56:21 +0800 Subject: [PATCH 01/91] init --- .../client/gui/GuiMainMenu.java.patch | 50 +- .../client/network/ServerPinger.java.patch | 18 +- .../network/NetworkManager.java.patch | 18 +- .../fml/client/FMLClientHandler.java | 3 +- .../fml/client/TEMPmodlist/ScreenUtil.java | 75 ++ .../screen/CatalogueModListScreen.java | 935 ++++++++++++++++++ .../widget/CatalogueCheckBoxButton.java | 53 + .../screen/widget/CatalogueIconButton.java | 68 ++ .../screen/widget/CatalogueListExtended.java | 157 +++ .../screen/widget/CatalogueTextField.java | 104 ++ .../assets/forge/textures/gui/checkbox.png | Bin 0 -> 2897 bytes .../assets/forge/textures/gui/icons.png | Bin 0 -> 2455 bytes .../forge/textures/gui/missing_banner.png | Bin 0 -> 57786 bytes src/main/resources/forge_at.cfg | 9 + 14 files changed, 1440 insertions(+), 50 deletions(-) create mode 100644 src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java create mode 100644 src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java create mode 100644 src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java create mode 100644 src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java create mode 100644 src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java create mode 100644 src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueTextField.java create mode 100644 src/main/resources/assets/forge/textures/gui/checkbox.png create mode 100644 src/main/resources/assets/forge/textures/gui/icons.png create mode 100644 src/main/resources/assets/forge/textures/gui/missing_banner.png diff --git a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch index 522116505..a5c5709c8 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch @@ -8,7 +8,15 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; -@@ -57,35 +58,30 @@ +@@ -29,6 +30,7 @@ + import net.minecraft.world.WorldServerDemo; + import net.minecraft.world.storage.ISaveFormat; + import net.minecraft.world.storage.WorldInfo; ++import net.minecraftforge.fml.client.TEMPmodlist.screen.CatalogueModListScreen; + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + import org.apache.commons.io.IOUtils; +@@ -57,35 +59,30 @@ private int field_92020_v; private int field_92019_w; private String field_92025_p; @@ -50,7 +58,7 @@ iresource = Minecraft.func_71410_x().func_110442_L().func_110536_a(field_110353_x); BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(iresource.func_110527_b(), StandardCharsets.UTF_8)); String s; -@@ -102,19 +98,24 @@ +@@ -102,19 +99,24 @@ if (!list.isEmpty()) { @@ -79,7 +87,7 @@ } this.field_73974_b = field_175374_h.nextFloat(); -@@ -130,30 +131,18 @@ +@@ -130,30 +132,18 @@ private boolean func_183501_a() { @@ -113,7 +121,7 @@ public void func_73866_w_() { this.field_73977_n = new DynamicTexture(256, 256); -@@ -198,40 +187,27 @@ +@@ -198,40 +188,27 @@ this.field_92024_r = this.field_146289_q.func_78256_a(this.field_146972_A); int k = Math.max(this.field_92023_s, this.field_92024_r); this.field_92022_t = (this.field_146294_l - k) / 2; @@ -158,7 +166,7 @@ ISaveFormat isaveformat = this.field_146297_k.func_71359_d(); WorldInfo worldinfo = isaveformat.func_75803_c("Demo_World"); -@@ -241,7 +217,6 @@ +@@ -241,7 +218,6 @@ } } @@ -166,7 +174,7 @@ protected void func_146284_a(GuiButton p_146284_1_) throws IOException { if (p_146284_1_.field_146127_k == 0) -@@ -264,16 +239,16 @@ +@@ -264,16 +240,16 @@ this.field_146297_k.func_147108_a(new GuiMultiplayer(this)); } @@ -182,13 +190,13 @@ + if (p_146284_1_.field_146127_k == 6) + { -+ this.field_146297_k.func_147108_a(new net.minecraftforge.fml.client.GuiModList(this)); ++ this.field_146297_k.func_147108_a(new CatalogueModListScreen(this)); + } + if (p_146284_1_.field_146127_k == 11) { this.field_146297_k.func_71371_a("Demo_World", "Demo_World", WorldServerDemo.field_73071_a); -@@ -286,28 +261,15 @@ +@@ -286,28 +262,15 @@ if (worldinfo != null) { @@ -218,7 +226,7 @@ public void func_73878_a(boolean p_73878_1_, int p_73878_2_) { if (p_73878_1_ && p_73878_2_ == 12) -@@ -328,7 +290,7 @@ +@@ -328,7 +291,7 @@ try { Class oclass = Class.forName("java.awt.Desktop"); @@ -227,7 +235,7 @@ oclass.getMethod("browse", URI.class).invoke(object, new URI(this.field_104024_v)); } catch (Throwable throwable) -@@ -359,12 +321,10 @@ +@@ -359,12 +322,10 @@ GlStateManager.func_179118_c(); GlStateManager.func_179129_p(); GlStateManager.func_179132_a(false); @@ -242,7 +250,7 @@ { GlStateManager.func_179094_E(); float f = ((float)(j % 8) / 8.0F - 0.5F) / 64.0F; -@@ -374,7 +334,7 @@ +@@ -374,7 +335,7 @@ GlStateManager.func_179114_b(MathHelper.func_76126_a(this.field_73979_m / 400.0F) * 25.0F + 20.0F, 1.0F, 0.0F, 0.0F); GlStateManager.func_179114_b(-this.field_73979_m * 0.1F, 0.0F, 1.0F, 0.0F); @@ -251,7 +259,7 @@ { GlStateManager.func_179094_E(); -@@ -407,10 +367,10 @@ +@@ -407,10 +368,10 @@ bufferbuilder.func_181668_a(7, DefaultVertexFormats.field_181709_i); int l = 255 / (j + 1); float f3 = 0.0F; @@ -266,7 +274,7 @@ tessellator.func_78381_a(); GlStateManager.func_179121_F(); } -@@ -419,7 +379,7 @@ +@@ -419,7 +380,7 @@ GlStateManager.func_179135_a(true, true, true, false); } @@ -275,7 +283,7 @@ GlStateManager.func_179135_a(true, true, true, true); GlStateManager.func_179128_n(5889); GlStateManager.func_179121_F(); -@@ -437,9 +397,7 @@ +@@ -437,9 +398,7 @@ GlStateManager.func_187421_b(3553, 10240, 9729); GlStateManager.func_187443_a(3553, 0, 0, 0, 0, 0, 256, 256); GlStateManager.func_179147_l(); @@ -286,7 +294,7 @@ GlStateManager.func_179135_a(true, true, true, false); Tessellator tessellator = Tessellator.func_178181_a(); BufferBuilder bufferbuilder = tessellator.func_178180_c(); -@@ -447,28 +405,16 @@ +@@ -447,28 +406,16 @@ GlStateManager.func_179118_c(); int i = 3; @@ -320,7 +328,7 @@ } tessellator.func_78381_a(); -@@ -498,26 +444,13 @@ +@@ -498,26 +445,13 @@ Tessellator tessellator = Tessellator.func_178181_a(); BufferBuilder bufferbuilder = tessellator.func_178180_c(); bufferbuilder.func_181668_a(7, DefaultVertexFormats.field_181709_i); @@ -351,7 +359,7 @@ public void func_73863_a(int p_73863_1_, int p_73863_2_, float p_73863_3_) { this.field_73979_m += p_73863_3_; -@@ -532,7 +465,7 @@ +@@ -532,7 +466,7 @@ this.field_146297_k.func_110434_K().func_110577_a(field_110352_y); GlStateManager.func_179131_c(1.0F, 1.0F, 1.0F, 1.0F); @@ -360,7 +368,7 @@ { this.func_73729_b(j + 0, 30, 0, 0, 99, 44); this.func_73729_b(j + 99, 30, 129, 0, 27, 44); -@@ -548,10 +481,13 @@ +@@ -548,10 +482,13 @@ this.field_146297_k.func_110434_K().func_110577_a(field_194400_H); func_146110_a(j + 88, 67, 0.0F, 0.0F, 98, 14, 128.0F, 16.0F); @@ -375,7 +383,7 @@ f = f * 100.0F / (float)(this.field_146289_q.func_78256_a(this.field_73975_c) + 32); GlStateManager.func_179152_a(f, f, f); this.func_73732_a(this.field_146289_q, this.field_73975_c, 0, -8, -256); -@@ -567,14 +503,19 @@ +@@ -567,14 +504,19 @@ s = s + ("release".equalsIgnoreCase(this.field_146297_k.func_184123_d()) ? "" : "/" + this.field_146297_k.func_184123_d()); } @@ -401,7 +409,7 @@ { func_73734_a(this.field_193979_N, this.field_146295_m - 1, this.field_193979_N + this.field_193978_M, this.field_146295_m, -1); } -@@ -583,32 +524,21 @@ +@@ -583,32 +525,21 @@ { func_73734_a(this.field_92022_t - 2, this.field_92021_u - 2, this.field_92020_v + 2, this.field_92019_w - 1, 1428160512); this.func_73731_b(this.field_146289_q, this.field_92025_p, this.field_92022_t, this.field_92021_u, -1); @@ -437,7 +445,7 @@ { GuiConfirmOpenLink guiconfirmopenlink = new GuiConfirmOpenLink(this, this.field_104024_v, 13, true); guiconfirmopenlink.func_146358_g(); -@@ -616,26 +546,10 @@ +@@ -616,26 +547,10 @@ } } diff --git a/patches/minecraft/net/minecraft/client/network/ServerPinger.java.patch b/patches/minecraft/net/minecraft/client/network/ServerPinger.java.patch index 24a9c1429..900c8c0ae 100644 --- a/patches/minecraft/net/minecraft/client/network/ServerPinger.java.patch +++ b/patches/minecraft/net/minecraft/client/network/ServerPinger.java.patch @@ -1,24 +1,14 @@ --- before/net/minecraft/client/network/ServerPinger.java +++ after/net/minecraft/client/network/ServerPinger.java -@@ -167,11 +167,12 @@ +@@ -166,6 +166,7 @@ + p_147224_1_.func_147407_a(null); } + net.minecraftforge.fml.client.FMLClientHandler.instance().bindServerListData(p_147224_1_, serverstatusresponse); this.field_175092_e = Minecraft.func_71386_F(); networkmanager.func_179290_a(new CPacketPing(this.field_175092_e)); this.field_147403_d = true; - } - } - @Override - public void func_147398_a(SPacketPong p_147398_1_) - { - long i = this.field_175092_e; -@@ -190,12 +191,12 @@ - ServerPinger.this.func_147225_b(p_147224_1_); - } - } - } - ); +@@ -195,7 +196,7 @@ try { @@ -26,4 +16,4 @@ + networkmanager.func_179290_a(new C00Handshake(serveraddress.func_78861_a(), serveraddress.func_78864_b(), EnumConnectionState.STATUS, true)); networkmanager.func_179290_a(new CPacketServerQuery()); } - catch (Throwable throwable) \ No newline at end of file + catch (Throwable throwable) diff --git a/patches/minecraft/net/minecraft/network/NetworkManager.java.patch b/patches/minecraft/net/minecraft/network/NetworkManager.java.patch index 0cd087939..2e951e552 100644 --- a/patches/minecraft/net/minecraft/network/NetworkManager.java.patch +++ b/patches/minecraft/net/minecraft/network/NetworkManager.java.patch @@ -1,6 +1,6 @@ --- before/net/minecraft/network/NetworkManager.java +++ after/net/minecraft/network/NetworkManager.java -@@ -88,7 +88,12 @@ +@@ -88,6 +88,11 @@ this.field_179294_g = p_i46004_1_; } @@ -12,10 +12,7 @@ @Override public void channelActive(ChannelHandlerContext p_channelActive_1_) throws Exception { - super.channelActive(p_channelActive_1_); -@@ -208,9 +213,9 @@ - private void func_150732_b(final Packet p_150732_1_, @Nullable final GenericFutureListener > [] p_150732_2_) - { +@@ -210,7 +215,7 @@ final EnumConnectionState enumconnectionstate = EnumConnectionState.func_150752_a(p_150732_1_); final EnumConnectionState enumconnectionstate1 = this.field_150746_k.attr(field_150739_c).get(); @@ -33,10 +30,7 @@ { this.func_150723_a(enumconnectionstate); } -@@ -236,10 +241,10 @@ - { - this.field_150746_k.eventLoop().execute(new Runnable() - { +@@ -239,7 +244,7 @@ @Override public void run() { @@ -53,11 +47,7 @@ final NetworkManager networkmanager = new NetworkManager(EnumPacketDirection.CLIENTBOUND); Class oclass; LazyLoadBase lazyloadbase; -@@ -471,10 +477,15 @@ - } - else if (this.func_150729_e() != null) - { - this.func_150729_e().func_147231_a(new TextComponentTranslation("multiplayer.disconnect.generic")); +@@ -475,6 +481,11 @@ } } } diff --git a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java index f948621de..a8949729b 100644 --- a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java +++ b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java @@ -91,6 +91,7 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.common.util.CompoundDataFixer; +import net.minecraftforge.fml.client.TEMPmodlist.screen.CatalogueModListScreen; import net.minecraftforge.fml.client.registry.RenderingRegistry; import net.minecraftforge.fml.common.DummyModContainer; import net.minecraftforge.fml.common.DuplicateModsFoundException; @@ -744,7 +745,7 @@ public void tryLoadExistingWorld(GuiWorldSelection selectWorldGUI, WorldSummary public void showInGameModOptions(GuiIngameMenu guiIngameMenu) { - showGuiScreen(new GuiModList(guiIngameMenu)); + showGuiScreen(new CatalogueModListScreen(guiIngameMenu)); } public IModGuiFactory getGuiFactoryFor(ModContainer selectedMod) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java new file mode 100644 index 000000000..6745ad654 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java @@ -0,0 +1,75 @@ +package net.minecraftforge.fml.client.TEMPmodlist; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import org.lwjgl.opengl.GL11; + +/** + * Author: MrCrayfish + */ +public class ScreenUtil { + /** + * Creates a scissor test using minecraft screen coordinates instead of pixel coordinates. + * @param screenX + * @param screenY + * @param boxWidth + * @param boxHeight + */ + public static void scissor(int screenX, int screenY, int boxWidth, int boxHeight) { + Minecraft mc = Minecraft.getMinecraft(); + ScaledResolution scaledRes = new ScaledResolution(mc); + int scale = scaledRes.getScaleFactor(); + + int x = screenX * scale; + int y = mc.displayHeight - (screenY * scale + boxHeight * scale); + int width = Math.max(0, boxWidth * scale); + int height = Math.max(0, boxHeight * scale); + + GL11.glEnable(GL11.GL_SCISSOR_TEST); + GL11.glScissor(x, y, width, height); + } + + public static boolean isMouseWithin(int x, int y, int width, int height, int mouseX, int mouseY) { + return mouseX >= x && mouseX < x + width && mouseY >= y && mouseY < y + height; + } + + public static class Size2i { + public final int width, height; + + public Size2i(int width, int height) { + this.width = width; + this.height = height; + } + } + + /** + * A backport of AbstractGUI.blit. Should not be overused. + */ + public static void blit(int x, int y, int width, int height, float uOffset, float vOffset, int uWidth, int vHeight, int textureWidth, int textureHeight) { + float minU = uOffset / textureWidth; + float minV = vOffset / textureHeight; + float maxU = (uOffset + uWidth) / textureWidth; + float maxV = (vOffset + vHeight) / textureHeight; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + buffer.pos(x, y + height, 0).tex(minU, maxV).endVertex(); + buffer.pos(x + width, y + height, 0).tex(maxU, maxV).endVertex(); + buffer.pos(x + width, y, 0).tex(maxU, minV).endVertex(); + buffer.pos(x, y, 0).tex(minU, minV).endVertex(); + tessellator.draw(); + } + + /** + * A backport of AbstractGUI.blit. Should not be overused. + */ + public static void blit(int x, int y, float uOffset, float vOffset, int width, int height, int textureWidth, int textureHeight) { + blit(x, y, width, height, uOffset, vOffset, width, height, textureWidth, textureHeight); + } + +} diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java new file mode 100644 index 000000000..e0793f619 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -0,0 +1,935 @@ +package net.minecraftforge.fml.client.TEMPmodlist.screen; + +import com.google.common.collect.Lists; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.resources.IResourcePack; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.event.ClickEvent; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.client.FMLClientHandler; +import net.minecraftforge.fml.client.IModGuiFactory; +import net.minecraftforge.fml.client.TEMPmodlist.ScreenUtil; +import net.minecraftforge.fml.client.TEMPmodlist.ScreenUtil.Size2i; +import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueCheckBoxButton; +import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueIconButton; +import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueListExtended; +import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueTextField; +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.ModMetadata; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import org.apache.commons.lang3.tuple.Pair; +import org.lwjgl.opengl.GL11; + +import javax.annotation.Nullable; +import java.awt.Desktop; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.stream.Collectors; + +public class CatalogueModListScreen extends GuiScreen { + private static final ResourceLocation MISSING_BANNER = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/missing_banner.png"); + private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); + private static final Map> LOGO_CACHE = new HashMap<>(); + private static final Map> ICON_CACHE = new HashMap<>(); + private static final Map ITEM_CACHE = new HashMap<>(); + + private CatalogueTextField searchTextField; + private ModList modList; + private ModContainer selectedModInfo; + private CatalogueIconButton modFolderButton; + private CatalogueIconButton configButton; + private CatalogueIconButton websiteButton; + private CatalogueIconButton issueButton; + private CatalogueCheckBoxButton updatesButton; + + private List activeTooltip; + private int tooltipYOffset; + private StringList descriptionList; + + private long lastClickTime; + private GuiScreen mainMenu; + + /** + * @param mainMenu + */ + public CatalogueModListScreen(GuiScreen mainMenu) { + super(); + this.mainMenu = mainMenu; + } + + @Override + public void initGui() { + super.initGui(); + this.searchTextField = new CatalogueTextField(0, this.fontRenderer, 11, 25, 148, 20); + this.searchTextField.setCanLoseFocus(true); + this.searchTextField.setEnableBackgroundDrawing(true); + + this.modList = new ModList(); + this.modList.setSlotXBoundsFromLeft(10); + this.modList.setDrawTopAndBottom(false); + + this.buttonList.add(new GuiButton(1, 10, modList.bottom + 8, 127, 20, I18n.format("gui.back"))); + this.modFolderButton = this.addButton(new CatalogueIconButton(2, 140, modList.bottom + 8, 0, 0)); + + int padding = 10; + int contentLeft = this.modList.right + 12 + padding; + int contentWidth = this.width - contentLeft - padding; + int buttonWidth = (contentWidth - padding) / 3; + + this.configButton = this.addButton(new CatalogueIconButton(3, contentLeft, 105, 10, 0, buttonWidth, I18n.format("catalogue.gui.config"))); + this.configButton.visible = false; + + this.websiteButton = this.addButton(new CatalogueIconButton(4, contentLeft + buttonWidth + 5, 105, 20, 0, buttonWidth, I18n.format("catalogue.gui.website"))); + this.websiteButton.visible = false; + + this.issueButton = this.addButton(new CatalogueIconButton(5, contentLeft + buttonWidth + buttonWidth + 10, 105, 30, 0, buttonWidth, I18n.format("catalogue.gui.issue"))); + this.issueButton.visible = false; + + this.descriptionList = new StringList(contentWidth, this.height - 135 - 55, contentLeft, 130); + this.descriptionList.setDrawTopAndBottom(false); + this.descriptionList.setDrawBackground(false); + + this.updatesButton = this.addButton(new CatalogueCheckBoxButton(6, this.modList.right - 14, 7, false)); + + this.modList.filterAndUpdateList(this.searchTextField.getText()); + + // Resizing window causes all widgets to be recreated, therefore need to update selected info + if(this.selectedModInfo != null) { + this.setSelectedModInfo(this.selectedModInfo); + this.updateSelectedModList(); + ModEntry entry = this.modList.getEntryFromInfo(this.selectedModInfo); + if(entry != null) { + this.modList.centerScrollOn(entry); + } + } + this.updateSearchField(this.searchTextField.getText()); + } + + @Override + public void actionPerformed(GuiButton button) { + switch(button.id) { + case 1: + mc.displayGuiScreen(mainMenu); + break; + case 2: + try { + Desktop.getDesktop().open(Loader.instance().getConfigDir().getParentFile().toPath().resolve("mods").toFile()); + } catch (Exception e) { + FMLLog.log.error("Problem opening mods folder", e); + } + break; + case 3: + try { + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedModInfo); + GuiScreen newScreen = guiFactory.createConfigGui(this); + this.mc.displayGuiScreen(newScreen); + } catch (Exception e) { + FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", selectedModInfo.getModId(), e); + } + break; + case 4: + this.openLink(this.selectedModInfo); + break; + case 5: + //WIP + break; + case 6: + this.modList.filterAndUpdateList(this.searchTextField.getText()); + this.updateSelectedModList(); + break; + } + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + this.activeTooltip = null; + this.drawDefaultBackground(); + this.drawModList(mouseX, mouseY, partialTicks); + this.drawModInfo(mouseX, mouseY, partialTicks); + super.drawScreen(mouseX, mouseY, partialTicks); + + ModContainer info = Loader.instance().getIndexedModList().get("catalogue"); + if (info != null) this.loadAndCacheLogo(info); + Pair pair = LOGO_CACHE.get("catalogue"); + if (pair != null && pair.getLeft() != null) { + ResourceLocation textureId = pair.getLeft(); + Size2i size = pair.getRight(); + mc.getTextureManager().bindTexture(textureId); + ScreenUtil.blit(10, 9, 10, 10, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + } + + if (ScreenUtil.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { + this.setActiveTooltip(I18n.format("catalogue.gui.info")); + this.tooltipYOffset = 10; + } + + if (this.modFolderButton.isMouseOver()) { + this.setActiveTooltip(I18n.format("fml.button.open.mods.folder")); + } + + if (this.activeTooltip != null) { + this.drawHoveringText(this.activeTooltip, mouseX, mouseY + this.tooltipYOffset); + this.tooltipYOffset = 0; + } + } + + @Override + protected void keyTyped(char typedChar, int key) throws IOException { + if (this.searchTextField.textboxKeyTyped(typedChar, key)) { + String s = this.searchTextField.getText(); + this.updateSearchField(s); + this.modList.filterAndUpdateList(s); + return; + } + + super.keyTyped(typedChar, key); + } + + private class ModList extends CatalogueListExtended { + private List entries = Lists.newArrayList(); + private int selectedIndex = -1; + + public ModList() { + super(CatalogueModListScreen.this.mc, 150, CatalogueModListScreen.this.height, 46, CatalogueModListScreen.this.height - 35, 26); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + ScreenUtil.scissor(this.left, this.top, this.width, this.height); + super.drawScreen(mouseX, mouseY, partialTicks); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + } + + @Override + protected void elementClicked(int slotIndex, boolean isDoubleClick, int mouseX, int mouseY) { + selectedIndex = slotIndex; + CatalogueModListScreen.ModEntry entry = entries.get(slotIndex); + CatalogueModListScreen.this.setSelectedModInfo(entry.info); + } + + public void filterAndUpdateList(String text) { + List entries = Loader.instance().getActiveModList().stream() + .filter(info -> info.getName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) + .filter(info -> !updatesButton.selected() || ForgeVersion.getResult(info).status.shouldDraw()) + .map(info -> new ModEntry(info, this)) + .sorted(Comparator.comparing(entry -> entry.info.getName())) + .collect(Collectors.toList()); + this.entries = entries; + this.selectMod(this.getEntryFromInfo(selectedModInfo)); + this.setAmountScrolled(0); + } + + public ModEntry getEntryFromInfo(ModContainer info) { + return this.entries.stream().filter(entry -> entry.info == info).findFirst().orElse(null); + } + + public void selectMod(int selectedIndex) { + this.selectedIndex = selectedIndex; + } + + public void selectMod(IGuiListEntry entry) { + this.selectMod(this.getEntryIndex(entry)); + } + + public void centerScrollOn(IGuiListEntry entry) { + this.setAmountScrolled(this.slotHeight * this.getEntryIndex(entry)); + } + + @Override + public IGuiListEntry getListEntry(int index) { + return this.entries.get(index); + } + + public int getEntryIndex(IGuiListEntry entry) { + return this.entries.indexOf(entry); + } + + @Override + public int getListWidth() { + return this.width; // Why it is 220 by default??? + } + + @Override + protected int getSize() { + return this.entries.size(); + } + + @Override + protected boolean isSelected(int slotIndex) { + return slotIndex == selectedIndex; + } + + @Override + protected int getScrollBarX() { + return this.left + this.width - 6; + } + } + + private class ModEntry implements CatalogueListExtended.IGuiListEntry { + private final ModContainer info; + private final ModList list; + + public ModEntry(ModContainer info, ModList list) { + this.info = info; + this.list = list; + } + + @Override + public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { + left -= 2; // Move 2px, the borders. + // Draws mod name and version + drawString(fontRenderer, this.getFormattedModName(), left + 24, top + 2, 0xFFFFFF); + drawString(fontRenderer, TextFormatting.GRAY + this.info.getDisplayVersion(), left + 24, top + 12, 0xFFFFFF); + + //WIP + CatalogueModListScreen.this.loadAndCacheIcon(this.info); + + // Draw icon + if(ICON_CACHE.containsKey(this.info.getModId()) && ICON_CACHE.get(this.info.getModId()).getLeft() != null) { + ResourceLocation logoResource = TextureMap.LOCATION_MISSING_TEXTURE; + Size2i size = new Size2i(16, 16); + + Pair logoInfo = ICON_CACHE.get(this.info.getModId()); + if(logoInfo != null && logoInfo.getLeft() != null) { + logoResource = logoInfo.getLeft(); + size = logoInfo.getRight(); + } + + mc.getTextureManager().bindTexture(logoResource); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + ScreenUtil.blit(left + 4, top + 2, 16, 16, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + GlStateManager.disableBlend(); + } else { + try { + GlStateManager.enableDepth(); + RenderHelper.enableGUIStandardItemLighting(); + CatalogueModListScreen.this.mc.getRenderItem().renderItemIntoGUI(this.getItemIcon(), left + 4, top + 2); + GlStateManager.disableDepth(); + RenderHelper.disableStandardItemLighting(); + } catch(Exception e) { + ITEM_CACHE.put(this.info.getModId(), new ItemStack(Blocks.GRASS)); + } + } + + // Draws an icon if there is an update for the mod + ForgeVersion.CheckResult result = ForgeVersion.getResult(this.info); + if(result.status.shouldDraw()) { + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); + int vOffset = result.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; + ScreenUtil.blit(left + rowWidth - 8 - 10, top + 6, result.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); + } + } + + @Override + public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { + this.list.selectMod(slotIndex); + return true; + } + + private ItemStack getItemIcon() { + if(ITEM_CACHE.containsKey(this.info.getModId())) { + return ITEM_CACHE.get(this.info.getModId()); + } + + // Put grass as default item icon + ITEM_CACHE.put(this.info.getModId(), new ItemStack(Blocks.GRASS)); + + // Special case for Forge to set item icon to anvil + if(this.info.getModId().equals("forge")) { + ItemStack anvil = new ItemStack(Blocks.ANVIL); + ITEM_CACHE.put("forge", anvil); + return anvil; + } + + // Not working +// // Gets the raw item icon resource string +// String itemIcon = this.info.getCustomModProperties().get("catalogueItemIcon"); +// +// if(!itemIcon.isEmpty()) { +// try { +// String[] parts = itemIcon.split(":"); +// Item item = Item.getByNameOrId(parts[0]); +// int meta = parts.length > 1 ? Integer.parseInt(parts[1]) : 0; +// ItemStack itemStack = new ItemStack(item, 1, meta); +// ITEM_CACHE.put(this.info.getModId(), itemStack); +// return itemStack; +// } catch(Exception ignored) {} +// } + + // If the mod doesn't specify an item to use, Catalogue will attempt to get an item from the mod + Optional optional = ForgeRegistries.ITEMS.getValuesCollection().stream().filter(item -> item.getRegistryName().getNamespace().equals(this.info.getModId())).map(ItemStack::new).findFirst(); + if(optional.isPresent()) { + ItemStack item = optional.get(); + if(!item.isEmpty()) { + ITEM_CACHE.put(this.info.getModId(), item); + return item; + } + } + + return new ItemStack(Blocks.GRASS); + } + + private String getFormattedModName() { + String name = this.info.getName(); + int width = this.list.getListWidth() - (this.list.getMaxScroll() > 0 ? 30 : 24); + if(CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > width) { + name = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(name, width - 10) + "..."; + } + if(this.info.getModId().equals("forge") || this.info.getModId().equals("minecraft")) { + return TextFormatting.DARK_GRAY + name; + } + return name; + } + + @Override + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) {} + + @Override + public void updatePosition(int slotIndex, int x, int y, float partialTicks) {} + } + + private class StringList extends CatalogueListExtended { + private List entries = Lists.newArrayList(); + + public StringList(int width, int height, int left, int top) { + super(CatalogueModListScreen.this.mc, width, CatalogueModListScreen.this.height, top, top + height, 10); + this.setSlotXBoundsFromLeft(left); + } + + public void setTextFromInfo(ModContainer info) { + this.entries.clear(); + String description = info.getMetadata().description.trim(); + List lines = CatalogueModListScreen.this.fontRenderer.listFormattedStringToWidth(description, this.getListWidth()); + + for (String line : lines) { + String cleanLine = line.replace("\n", "").replace("\r", "").trim(); + this.entries.add(new StringEntry(cleanLine)); + } + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + ScreenUtil.scissor(this.left, this.top, this.width, this.bottom - this.top); + super.drawScreen(mouseX, mouseY, partialTicks); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + } + + @Override + protected int getScrollBarX() { + return this.left + this.width - 7; + } + + @Override + protected int getSize() { + return this.entries.size(); + } + + @Override + public int getListWidth() { + return this.width - 10; + } + + @Override + public IGuiListEntry getListEntry(int index) { + return this.entries.get(index); + } + } + + private class StringEntry implements CatalogueListExtended.IGuiListEntry { + private String line; + + public StringEntry(String line) { + this.line = line; + } + + @Override + public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { + drawString(CatalogueModListScreen.this.fontRenderer, this.line, left, top, 0xFFFFFF); + } + + @Override + public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { + return true; + } + + @Override + public void updatePosition(int slotIndex, int x, int y, float partialTicks) {} + + @Override + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) {} + } + + + @Override + public void handleMouseInput() throws IOException { + super.handleMouseInput(); + this.modList.handleMouseInput(); + this.descriptionList.handleMouseInput(); + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int button) throws IOException { + // Catalogue button + if (ScreenUtil.isMouseWithin(10, 9, 10, 10, mouseX, mouseY) && button == 0) { + this.openLink("https://www.curseforge.com/minecraft/mc-mods/catalogue"); + return; + } + + // Version check button + if (this.selectedModInfo != null) { + int contentLeft = this.modList.right + 12 + 10; + String version = I18n.format("catalogue.gui.version", this.selectedModInfo.getDisplayVersion()); + int versionWidth = this.fontRenderer.getStringWidth(version); + if (ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { + ForgeVersion.CheckResult result = ForgeVersion.getResult(this.selectedModInfo); + if (result.status.shouldDraw() && result.url != null) { + this.openLink(result.url); + } + } + } + + // Search Text Field + if (ScreenUtil.isMouseWithin(this.searchTextField.x, this.searchTextField.y, this.searchTextField.width, this.searchTextField.height, mouseX, mouseY)) { + // Right click to empty + if (button == 1) { + this.searchTextField.setText(""); + this.updateSearchField(""); + this.modList.filterAndUpdateList(""); + return; + } + // Left click to apply suggestions + if (button == 0) { + String text = this.searchTextField.getText(); + long currentTine = Minecraft.getSystemTime(); + if (!text.isEmpty() && currentTine - this.lastClickTime < 250L && !this.searchTextField.getIsTextTruncated()) { + text += this.searchTextField.getSuggestion(); + this.searchTextField.setText(text); + this.updateSearchField(text); + this.modList.filterAndUpdateList(text); + this.lastClickTime = currentTine; + return; + } + this.lastClickTime = currentTine; + } + } + this.searchTextField.mouseClicked(mouseX, mouseY, button); + + super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public void updateScreen() { + super.updateScreen(); + this.searchTextField.updateCursorCounter(); + } + + /** + * Draws everything considered left of the screen; title, search bar and mod list. + * + * @param mouseX the current mouse x position + * @param mouseY the current mouse y position + * @param partialTicks the partial ticks + */ + private void drawModList(int mouseX, int mouseY, float partialTicks) { + GlStateManager.enableBlend(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); + ScreenUtil.blit(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); + GlStateManager.disableBlend(); + + this.modList.drawScreen(mouseX, mouseY, partialTicks); + drawString(this.fontRenderer, TextFormatting.BOLD + I18n.format("catalogue.gui.title"), 70, 10, 0xFFFFFF); + this.searchTextField.drawTextBox(); + + if(ScreenUtil.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { + this.setActiveTooltip(I18n.format("catalogue.gui.filter_updates")); + this.tooltipYOffset = 10; + } + } + + /** + * Draws everything considered right of the screen; logo, mod title, description and more. + * + * @param mouseX the current mouse x position + * @param mouseY the current mouse y position + * @param partialTicks the partial ticks + */ + private void drawModInfo(int mouseX, int mouseY, float partialTicks) { + this.drawVerticalLine(this.modList.right + 11, -1, this.height, 0xFF707070); + drawRect(this.modList.right + 12, 0, this.width, this.height, 0x66000000); + this.descriptionList.drawScreen(mouseX, mouseY, partialTicks); + + int contentLeft = this.modList.right + 12 + 10; + int contentWidth = this.width - contentLeft - 10; + + if(this.selectedModInfo != null) { + // Draw mod logo + this.drawLogo(contentWidth, contentLeft, 10, this.width - (this.modList.right + 12 + 10) - 10, 50); + + // Draw mod name + GlStateManager.pushMatrix(); + GlStateManager.translate(contentLeft, 70, 0); + GlStateManager.scale(2.0F, 2.0F, 2.0F); + drawString(this.fontRenderer, this.selectedModInfo.getName(), 0, 0, 0xFFFFFF); + GlStateManager.popMatrix(); + + // Draw version + String modId = TextFormatting.DARK_GRAY + I18n.format("catalogue.gui.modid", this.selectedModInfo.getModId()); + int modIdWidth = this.fontRenderer.getStringWidth(modId); + drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); + +// // Set tooltip for secure mod features forge has +// if(ScreenUtil.isMouseWithin(contentLeft + contentWidth - modIdWidth, 92, modIdWidth, this.font.lineHeight, mouseX, mouseY)) { +// if(FMLEnvironment.secureJarsEnabled) { +// this.setActiveTooltip(ForgeI18n.parseMessage("fml.menu.mods.info.signature", ((ModInfo) this.selectedModInfo).getOwningFile().getCodeSigningFingerprint().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.signature.unsigned")))); +// this.setActiveTooltip(ForgeI18n.parseMessage("fml.menu.mods.info.trust", ((ModInfo) this.selectedModInfo).getOwningFile().getTrustData().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.trust.noauthority")))); +// } else { +// this.setActiveTooltip(ForgeI18n.parseMessage("fml.menu.mods.info.securejardisabled")); +// } +// } + + // Draw version + String displayVersion = this.selectedModInfo.getDisplayVersion(); + this.drawStringWithLabel("catalogue.gui.version", displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + + // Draw inner version if the display version is different from it + int versionWidth = this.fontRenderer.getStringWidth(I18n.format("catalogue.gui.version", displayVersion)); + String innerVersion = this.selectedModInfo.getVersion(); + if (!displayVersion.equals(innerVersion) && ScreenUtil.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { + this.setActiveTooltip(innerVersion); + } + + // Draws an icon if there is an update for the mod + ForgeVersion.CheckResult result = ForgeVersion.getResult(this.selectedModInfo); + if(result.status.shouldDraw() && result.url != null) { + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); + int vOffset = result.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; + ScreenUtil.blit(contentLeft + versionWidth + 5, 92, result.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); + if(ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { + this.setActiveTooltip(I18n.format("catalogue.gui.update_available", result.url)); + } + } + + ModMetadata metadata = selectedModInfo.getMetadata(); + if (metadata != null && !metadata.autogenerated) { + + int labelOffset = this.height - 20; + +// // Draw license +// String license = this.selectedModInfo.getMetadata().getLicense(); +// this.drawStringWithLabel("fml.menu.mods.info.license", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); +// labelOffset -= 15; + + // Draw credits + String credits = metadata.credits; + if(!credits.isEmpty()) { + this.drawStringWithLabel("catalogue.gui.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + labelOffset -= 15; + } + + // Draw authors + String authors = metadata.getAuthorList(); + if(!authors.isEmpty()) { + this.drawStringWithLabel("catalogue.gui.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + } + } + } else { + String message = TextFormatting.GRAY + I18n.format("catalogue.gui.no_selection"); + drawCenteredString(this.fontRenderer, message, contentLeft + contentWidth / 2, this.height / 2 - 5, 0xFFFFFF); + } + } + + /** + * Draws a string and prepends a label. If the formed string and label is longer than the + * specified max width, it will automatically be trimmed and allows the user to hover the + * string with their mouse to read the full contents. + * + * @param format a string to prepend to the content + * @param text the string to render + * @param x the x position + * @param y the y position + * @param maxWidth the maximum width the string can render + * @param mouseX the current mouse x position + * @param mouseY the current mouse u position + */ + @SuppressWarnings("SameParameterValue") + private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { + String formatted = I18n.format(format, text); // Attempting to keep Forge's lang since it's already support many languages + String label = formatted.substring(0, formatted.indexOf(":") + 1); + String content = formatted.substring(formatted.indexOf(":") + 1); + if(this.fontRenderer.getStringWidth(formatted) > maxWidth) { + content = this.fontRenderer.trimStringToWidth(content, maxWidth - this.fontRenderer.getStringWidth(label) - 7) + "..."; + String credits = labelColor + label; + credits += contentColor + content; + drawString(this.fontRenderer, credits, x, y, 0xFFFFFF); + if(ScreenUtil.isMouseWithin(x, y, maxWidth, 9, mouseX, mouseY)) { // Sets the active tool tip if string is too long so users can still read it + this.setActiveTooltip(text); + } + } else { + drawString(this.fontRenderer, labelColor + label + contentColor + content, x, y, 0xFFFFFF); + } + } + + private void loadAndCacheLogo(ModContainer info) { + if (LOGO_CACHE.containsKey(info.getModId())) return; + + // Fills an empty logo as logo may not be present + LOGO_CACHE.put(info.getModId(), Pair.of(null, new Size2i(0, 0))); + + // Attempts to load the real logo + ModContainer modInfo = (ModContainer) info; + ModMetadata metadata = modInfo.getMetadata(); + if (metadata == null) return; + String s = metadata.logoFile; + if (s.isEmpty()) return; + +// if(s.contains("/") || s.contains("\\")) { +// Catalogue.LOGGER.warn("Skipped loading logo file from {}. The file name '{}' contained illegal characters '/' or '\\'", info.getName(), s); +// return; +// } + + IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); + BufferedImage logo = null; + try { + // Amazing. Forge's banner cannot be loaded though its resource pack is not null. + if (resourcePack != null && !info.getModId().equals("forge")) { + logo = resourcePack.getPackImage(); + } else { + InputStream is = getClass().getResourceAsStream(s); + if (is != null) logo = TextureUtil.readBufferedImage(is); + } + if (logo == null) return; + TextureManager textureManager = this.mc.getTextureManager(); + LOGO_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("modlogo", this.createLogoTexture(logo, true)), new Size2i(logo.getWidth(), logo.getHeight()))); + } catch (IOException ignored) {} + } + + private void loadAndCacheIcon(ModContainer info) { + if(ICON_CACHE.containsKey(info.getModId())) + return; + + // Fills an empty icon as icon may not be present + ICON_CACHE.put(info.getModId(), Pair.of(null, new Size2i(0, 0))); + + // Not working + // Attempts to load the real icon + ModContainer modInfo = (ModContainer) info; + if (modInfo.getCustomModProperties().containsKey("catalogueImageIcon")) { + String s = (String) modInfo.getCustomModProperties().get("catalogueImageIcon"); + + if (s.isEmpty()) return; + +// if (s.contains("/") || s.contains("\\")) { +// Catalogue.LOGGER.warn("Skipped loading Catalogue icon file from {}. The file name '{}' contained illegal characters '/' or '\\'", info.getName(), s); +// return; +// } + + try (InputStream is = getClass().getResourceAsStream(s)) { + BufferedImage icon = null; + if (is != null) icon = TextureUtil.readBufferedImage(is); + if (icon == null) return; + TextureManager textureManager = this.mc.getTextureManager(); + ICON_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(icon, true)), new Size2i(icon.getWidth(), icon.getHeight()))); + return; + } catch (IOException ignored) {} + } + + // Attempts to use the logo file if it's a square + ModMetadata metadata = modInfo.getMetadata(); + if (metadata == null) return; + String s = metadata.logoFile; + if (s.isEmpty()) return; + +// if (s.contains("/") || s.contains("\\")) { +// Catalogue.LOGGER.warn("Skipped loading logo file from {}. The file name '{}' contained illegal characters '/' or '\\'", info.getName(), s); +// return; +// } + + IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); + BufferedImage logo = null; + try { + if (resourcePack != null) { + logo = resourcePack.getPackImage(); + } else { + InputStream is = getClass().getResourceAsStream(s); + if (is != null) logo = TextureUtil.readBufferedImage(is); + } + if (logo == null) return; + if (logo.getWidth() == logo.getHeight()) { + TextureManager textureManager = this.mc.getTextureManager(); + String modId = info.getModId(); + + /* The first selected mod will have its logo cached before the icon, so we + * can just use the logo instead of loading the image again. */ + if (LOGO_CACHE.containsKey(modId)) { + if (LOGO_CACHE.get(modId).getLeft() != null) { + ICON_CACHE.put(modId, LOGO_CACHE.get(modId)); + return; + } + } + + /* Since the icon will be same as the logo, we can cache into both icon and logo cache */ + DynamicTexture texture = this.createLogoTexture(logo, true); + Size2i size = new Size2i(logo.getWidth(), logo.getHeight()); + ResourceLocation textureId = textureManager.getDynamicTextureLocation("catalogueicon", texture); + ICON_CACHE.put(modId, Pair.of(textureId, size)); + LOGO_CACHE.put(modId, Pair.of(textureId, size)); + } + } catch (IOException ignored) {} + } + + private DynamicTexture createLogoTexture(BufferedImage image, boolean smooth) { + return new DynamicTexture(image) { + @Override + public void updateDynamicTexture() { + TextureUtil.uploadTextureImageAllocate(this.getGlTextureId(), image, smooth, false); + } + }; + } + + @SuppressWarnings("SameParameterValue") + private void drawLogo(int contentWidth, int x, int y, int maxWidth, int maxHeight) { + if(this.selectedModInfo != null) { + ResourceLocation logoResource = MISSING_BANNER; + Size2i size = new Size2i(600, 120); + + if(LOGO_CACHE.containsKey(this.selectedModInfo.getModId())) { + Pair logoInfo = LOGO_CACHE.get(this.selectedModInfo.getModId()); + if(logoInfo.getLeft() != null) { + logoResource = logoInfo.getLeft(); + size = logoInfo.getRight(); + } + } + + mc.getTextureManager().bindTexture(logoResource); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + + int width = size.width; + int height = size.height; + if (size.width > maxWidth) { + width = maxWidth; + height = (width * size.height) / size.width; + } + if (height > maxHeight) { + height = maxHeight; + width = (height * size.width) / size.height; + } + + x += (contentWidth - width) / 2; + y += (maxHeight - height) / 2; + + ScreenUtil.blit(x, y, width, height, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + + GlStateManager.disableBlend(); + } + } + + private void setActiveTooltip(String content) { + this.activeTooltip = this.fontRenderer.listFormattedStringToWidth(content, 200); + this.tooltipYOffset = 0; + } + + private void setSelectedModInfo(ModContainer selectedModInfo) { + this.selectedModInfo = selectedModInfo; + this.loadAndCacheLogo(selectedModInfo); + this.configButton.visible = true; + this.websiteButton.visible = true; + this.issueButton.visible = true; + + this.configButton.enabled = false; + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedModInfo); + if (guiFactory != null) { + this.configButton.enabled = guiFactory.hasConfigGui(); + } + + ModMetadata metadata = selectedModInfo.getMetadata(); + if (metadata != null && !metadata.autogenerated) { + this.websiteButton.enabled = !metadata.url.isEmpty(); + } + + this.issueButton.enabled = false; +// this.issueButton.active = ((ModInfo) selectedModInfo).getOwningFile().getConfigElement("issueTrackerURL").isPresent(); + + int contentLeft = this.modList.right + 12 + 10; + int contentWidth = this.width - contentLeft - 10; + int labelCount = this.getLabelCount(selectedModInfo); + this.descriptionList.updateSize(contentWidth, this.height - 135 - 10 - labelCount * 15, 130, this.height - 10 - labelCount * 15); + this.descriptionList.setSlotXBoundsFromLeft(contentLeft); + this.descriptionList.setTextFromInfo(selectedModInfo); + this.descriptionList.setAmountScrolled(0); + } + + private int getLabelCount(ModContainer selectedModInfo) { + int count = 1; //1 by default since license property will always exist + + ModMetadata metadata = selectedModInfo.getMetadata(); + if (metadata != null && !metadata.autogenerated) { + if (!metadata.credits.isEmpty()) count++; + if (!metadata.authorList.isEmpty()) count++; + } + + return count; + } + + private void updateSelectedModList() { + ModEntry selectedEntry = this.modList.getEntryFromInfo(this.selectedModInfo); + if(selectedEntry != null) { + this.modList.selectMod(selectedEntry); + } + } + + private void updateSearchField(String value) { + if(value.isEmpty()) { + this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search")); + } else { + Optional optional = Loader.instance().getActiveModList().stream().filter(info -> { + return info.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); + }).min(Comparator.comparing(ModContainer::getName)); + if(optional.isPresent()) { + int length = value.length(); + String displayName = optional.get().getName(); + this.searchTextField.setSuggestion(displayName.substring(length)); + } else { + this.searchTextField.setSuggestion(""); + } + } + } + + /** + * Opens a link with a url defined in the mod's info + */ + private void openLink(@Nullable ModContainer configurable) { + if(configurable != null) { + ModMetadata metadata = configurable.getMetadata(); + // The config button is only enabled when checked, so it is unnecessary to check again. + openLink(metadata.url); + } + } + + private void openLink(String url) { + Style style = new Style().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)); + this.handleComponentClick(new TextComponentString("").setStyle(style)); + } +} diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java new file mode 100644 index 000000000..e6f671db2 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java @@ -0,0 +1,53 @@ +package net.minecraftforge.fml.client.TEMPmodlist.screen.widget; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.ForgeVersion; + +/** + * Author: MrCrayfish + */ +public class CatalogueCheckBoxButton extends GuiButton { + private static final ResourceLocation TEXTURE = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/checkbox.png"); + private boolean selected; + + public CatalogueCheckBoxButton(int id, int x, int y, boolean selectedDefault) { + super(id, x, y, 14, 14, ""); + this.selected = selectedDefault; + } + + @Override + public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY) { + if (this.enabled && this.visible && this.hovered) { + this.selected = !this.selected; + return true; + } else { + return false; + } + } + + @Override + public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partial) { + if (this.visible) { + this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + minecraft.getTextureManager().bindTexture(TEXTURE); + + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + this.drawTexturedModalRect(this.x, this.y, this.hovered ? 14 : 0, this.selected ? 14 : 0, this.width, this.height); + this.mouseDragged(minecraft, mouseX, mouseY); + } + } + + public boolean selected() { + return this.selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } +} diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java new file mode 100644 index 000000000..390676582 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java @@ -0,0 +1,68 @@ +package net.minecraftforge.fml.client.TEMPmodlist.screen.widget; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraftforge.common.ForgeVersion; + +/** + * Author: MrCrayfish + */ +public class CatalogueIconButton extends GuiButton { + private static final ResourceLocation TEXTURE = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/icons.png"); + + private final String label; + private final int u, v; + + public CatalogueIconButton(int id, int x, int y, int u, int v) { + this(id, x, y, u, v, 20, ""); + } + + public CatalogueIconButton(int id, int x, int y, int u, int v, int width, String label) { + super(id, x, y, width, 20, ""); + this.label = label; + this.u = u; + this.v = v; + } + + @Override + public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks) { + // Draw bg + super.drawButton(minecraft, mouseX, mouseY, partialTicks); + // Draw icon and text + if (this.visible) { + FontRenderer fontrenderer = minecraft.fontRenderer; + minecraft.getTextureManager().bindTexture(TEXTURE); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + int contentWidth = 10 + fontrenderer.getStringWidth(this.label) + (!this.label.isEmpty() ? 4 : 0); + int iconX = this.x + (this.width - contentWidth) / 2; + int iconY = this.y + 5; + float brightness = this.enabled ? 1.0F : 0.5F; + GlStateManager.color(brightness, brightness, brightness, 1.0F); + this.drawTexturedModalRect(iconX, iconY, this.u, this.v, 10, 10); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + int textColor = this.getFGColor() | MathHelper.ceil(255.0F) << 24; + drawString(fontrenderer, this.label, iconX + 14, iconY + 1, textColor); + } + + } + + private int getFGColor(){ + if (packedFGColour != 0) { + return packedFGColour; + } else if (!this.enabled) { + return 10526880; + } else if (this.hovered) { + return 16777120; + } else { + return 14737632; + } + } + +} diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java new file mode 100644 index 000000000..5c8c0e3b7 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java @@ -0,0 +1,157 @@ +package net.minecraftforge.fml.client.TEMPmodlist.screen.widget; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiListExtended; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; + +public abstract class CatalogueListExtended extends GuiListExtended { + // Noting different from the original one, but allows you to remove the shadow on the bottom and top. + // Created to avoid mixins. + private boolean shouldDrawTopAndBottom = true; + private boolean shouldDrawBackground = true; + + public CatalogueListExtended(Minecraft mcIn, int widthIn, int heightIn, int topIn, int bottomIn, int slotHeightIn) { + super(mcIn, widthIn, heightIn, topIn, bottomIn, slotHeightIn); + } + + // Values renamed by deepseek. Comments are handwrite. + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + if (!this.visible) return; + + this.mouseX = mouseX; + this.mouseY = mouseY; + + // Customized background. Empty by default. + this.drawBackground(); + + int scrollBarLeft = this.getScrollBarX(); + int scrollBarRight = scrollBarLeft + 6; + + this.bindAmountScrolled(); + GlStateManager.disableLighting(); + GlStateManager.disableFog(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder vertexBuffer = tessellator.getBuffer(); + + // Shadowed dirt background. Scroll with the entries. + if (this.shouldDrawBackground) { + this.drawContainerBackground(tessellator); + } + + int contentLeft = this.left + (this.shouldDrawBackground ? this.width / 2 - this.getListWidth() / 2 + 2 : 0); + int contentTop = this.top + 4 - (int)this.amountScrolled; + + if (this.hasListHeader) { + this.drawListHeader(contentLeft, contentTop, tessellator); + } + + this.drawSelectionBox(contentLeft, contentTop, mouseX, mouseY, partialTicks); + + GlStateManager.disableDepth(); + + // Draw overlay to hide scrolled entries + this.overlayBackground(0, this.top, 255, 255); + this.overlayBackground(this.bottom, this.height, 255, 255); + + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ZERO, GlStateManager.DestFactor.ONE); + GlStateManager.disableAlpha(); + GlStateManager.shadeModel(GL11.GL_SMOOTH); + GlStateManager.disableTexture2D(); + + // Shadow the top and bottom + if (this.shouldDrawTopAndBottom) { + this.drawTopAndBottom(tessellator); + } + + // Scroll Bar + int maxScroll = this.getMaxScroll(); + if (maxScroll > 0 && this.getContentHeight() != 0) { + int scrollThumbHeight = (this.bottom - this.top) * (this.bottom - this.top) / this.getContentHeight(); + scrollThumbHeight = MathHelper.clamp(scrollThumbHeight, 32, this.bottom - this.top - 8); + int scrollThumbTop = (int)this.amountScrolled * (this.bottom - this.top - scrollThumbHeight) / maxScroll + this.top; + scrollThumbTop = Math.max(scrollThumbTop, this.top); + + // Background + vertexBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + vertexBuffer.pos(scrollBarLeft, this.bottom, 0).tex(0, 1).color(0, 0, 0, 255).endVertex(); + vertexBuffer.pos(scrollBarRight, this.bottom, 0).tex(1, 1).color(0, 0, 0, 255).endVertex(); + vertexBuffer.pos(scrollBarRight, this.top, 0).tex(1, 0).color(0, 0, 0, 255).endVertex(); + vertexBuffer.pos(scrollBarLeft, this.top, 0).tex(0, 0).color(0, 0, 0, 255).endVertex(); + tessellator.draw(); + + // Main + vertexBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + vertexBuffer.pos(scrollBarLeft, scrollThumbTop + scrollThumbHeight, 0).tex(0, 1).color(128, 128, 128, 255).endVertex(); + vertexBuffer.pos(scrollBarRight, scrollThumbTop + scrollThumbHeight, 0).tex(1, 1).color(128, 128, 128, 255).endVertex(); + vertexBuffer.pos(scrollBarRight, scrollThumbTop, 0).tex(1, 0).color(128, 128, 128, 255).endVertex(); + vertexBuffer.pos(scrollBarLeft, scrollThumbTop, 0).tex(0, 0).color(128, 128, 128, 255).endVertex(); + tessellator.draw(); + + // Border + vertexBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + vertexBuffer.pos(scrollBarLeft, scrollThumbTop + scrollThumbHeight - 1, 0).tex(0, 1).color(192, 192, 192, 255).endVertex(); + vertexBuffer.pos(scrollBarRight - 1, scrollThumbTop + scrollThumbHeight - 1, 0).tex(1, 1).color(192, 192, 192, 255).endVertex(); + vertexBuffer.pos(scrollBarRight - 1, scrollThumbTop, 0).tex(1, 0).color(192, 192, 192, 255).endVertex(); + vertexBuffer.pos(scrollBarLeft, scrollThumbTop, 0).tex(0, 0).color(192, 192, 192, 255).endVertex(); + tessellator.draw(); + } + + // Customized decorations. Empty by default. + this.renderDecorations(mouseX, mouseY); + + GlStateManager.enableTexture2D(); + GlStateManager.shadeModel(GL11.GL_FLAT); + GlStateManager.enableAlpha(); + GlStateManager.disableBlend(); + } + + protected void drawTopAndBottom(Tessellator tessellator) { + BufferBuilder buffer = tessellator.getBuffer(); + buffer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); + buffer.pos((double)this.left, (double)(this.top + 4), 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 0).endVertex(); + buffer.pos((double)this.right, (double)(this.top + 4), 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 0).endVertex(); + buffer.pos((double)this.right, (double)this.top, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos((double)this.left, (double)this.top, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 255).endVertex(); + tessellator.draw(); + buffer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); + buffer.pos((double)this.left, (double)this.bottom, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos((double)this.right, (double)this.bottom, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos((double)this.right, (double)(this.bottom - 4), 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 0).endVertex(); + buffer.pos((double)this.left, (double)(this.bottom - 4), 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 0).endVertex(); + tessellator.draw(); + } + + public void setAmountScrolled(int amountScrolled) { + this.amountScrolled = (float)amountScrolled; + this.bindAmountScrolled(); + this.initialClickY = -2; + } + + public void updateSize(int width, int height, int top, int bottom) { + this.width = width; + this.height = height; + this.top = top; + this.bottom = bottom; + } + + /** + * Sets whether draw the top and bottom shadow. + */ + public void setDrawTopAndBottom(boolean shouldDraw) { + this.shouldDrawTopAndBottom = shouldDraw; + } + + /** + * Sets whether draw the shadowed dirt background, which scrolls with the entries. + */ + public void setDrawBackground(boolean shouldDraw) { + this.shouldDrawBackground = shouldDraw; + } +} diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueTextField.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueTextField.java new file mode 100644 index 000000000..9eafff80d --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueTextField.java @@ -0,0 +1,104 @@ +package net.minecraftforge.fml.client.TEMPmodlist.screen.widget; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiTextField; + +public class CatalogueTextField extends GuiTextField { + // GuiTextField with suggestions. + // Also, lit it! + + private final FontRenderer fontRenderer; + private String suggestion = ""; + private boolean isTextTruncated; + + public CatalogueTextField(int id, FontRenderer fontRenderer, int x, int y, int width, int height) { + super(id, fontRenderer, x, y, width, height); + this.fontRenderer = fontRenderer; + } + + // Values renamed by deepseek. Comments are handwrite. + @Override + public void drawTextBox() { + if (!this.getVisible()) return; + + if (this.getEnableBackgroundDrawing()) { + int borderColor = this.isFocused() ? 0xFFFFFFFF : 0xFFA0A0A0; + drawRect(this.x - 1, this.y - 1, this.x + this.width + 1, this.y + this.height + 1, borderColor); + drawRect(this.x, this.y, this.x + this.width, this.y + this.height, 0xFF000000); + } + + int textColor = this.isEnabled ? this.enabledColor : this.disabledColor; + + int cursorPosRelative = this.cursorPosition - this.lineScrollOffset; + int selectionEndRelative = this.selectionEnd - this.lineScrollOffset; + + String visibleText = this.fontRenderer.trimStringToWidth(this.getText().substring(this.lineScrollOffset), this.getWidth()); + + boolean isCursorVisible = cursorPosRelative >= 0 && cursorPosRelative <= visibleText.length(); + boolean shouldDrawCursor = this.isFocused() && this.cursorCounter / 6 % 2 == 0 && isCursorVisible; + + int textStartX = this.getEnableBackgroundDrawing() ? this.x + 4 : this.x; + int textStartY = this.getEnableBackgroundDrawing() ? this.y + (this.height - 8) / 2 : this.y; + int currentDrawX = textStartX; + + if (selectionEndRelative > visibleText.length()) { + selectionEndRelative = visibleText.length(); + } + + // Draw text before cursor + if (!visibleText.isEmpty()) { + String text = isCursorVisible ? visibleText.substring(0, cursorPosRelative) : visibleText; + currentDrawX = this.fontRenderer.drawStringWithShadow(text, (float) textStartX, (float) textStartY, textColor); + } + + isTextTruncated = this.cursorPosition < this.getText().length() || this.getText().length() >= this.getMaxStringLength(); + int cursorDrawX = currentDrawX; + + if (!isCursorVisible) { + cursorDrawX = cursorPosRelative > 0 ? textStartX + this.width : textStartX; + } else if (isTextTruncated) { + cursorDrawX = currentDrawX - 1; + --currentDrawX; + } + + // Draw text after cursor + if (!visibleText.isEmpty() && isCursorVisible && cursorPosRelative < visibleText.length()) { + currentDrawX = this.fontRenderer.drawStringWithShadow(visibleText.substring(cursorPosRelative), (float) currentDrawX, (float) textStartY, textColor); + } + + if (!isTextTruncated && this.suggestion != null) { + if (!this.getText().isEmpty()) { + this.fontRenderer.drawStringWithShadow(this.suggestion, (float) currentDrawX - 1, (float) textStartY, 0x808080); + } else { + this.fontRenderer.drawStringWithShadow(this.suggestion, (float) currentDrawX, (float) textStartY, 0x808080); + } + } + + if (shouldDrawCursor) { + if (isTextTruncated) { + Gui.drawRect(cursorDrawX, textStartY - 1, cursorDrawX + 1, textStartY + 1 + this.fontRenderer.FONT_HEIGHT, 0xFFCFCFD0); + } else { + this.fontRenderer.drawStringWithShadow("_", (float) cursorDrawX, (float) textStartY, textColor); + } + } + + if (selectionEndRelative != cursorPosRelative) { + int selectionEndX = textStartX + this.fontRenderer.getStringWidth(visibleText.substring(0, selectionEndRelative)); + this.drawSelectionBox(cursorDrawX, textStartY - 1, selectionEndX - 1, textStartY + 1 + this.fontRenderer.FONT_HEIGHT); + } + } + + public void setSuggestion(String suggestion) { + this.suggestion = suggestion; + } + + public String getSuggestion() { + return this.suggestion; + } + + public boolean getIsTextTruncated() { + return this.isTextTruncated; + } + +} diff --git a/src/main/resources/assets/forge/textures/gui/checkbox.png b/src/main/resources/assets/forge/textures/gui/checkbox.png new file mode 100644 index 0000000000000000000000000000000000000000..ab88fcb5fa7035ac6ab497c6bc0f26c889c22413 GIT binary patch literal 2897 zcmb_eYj6`)6uv=(7KZ}DI1Cn8Q^yzG?A=W^kCi|%t+d*-Jc1Nd%FXUgvXW%O?zT;< zLgWX^48jiwh4RvIMjV`iIOF)}2t|2_JVY2oVVIVxARtf_u(gGHH%((@fL6wxWcKd8 z=X~e;&OP_+E}2p?dGMf7g8%@7iwg6n0bn9S6UZ1qZqKRj2gofmRya!o;GW^e-vnyc zj09l79@$r>mwAf?QHfZPr1-HV9*Gfa0LD*@$B;M=>y#e{<*3_yv3{GGk|no!Chvvb zm!MjG@GimTpi-Ba#8ah!FfISd zR5bGBHivXQCNNA@Rh6a6YEjf6@pZWv$T1v86AN0aj_N2*N41=8gnX=tsvOg0B}y5H z$gfoBZZqN9B_a|_!$!4KqDab^IEpc>1sbA~hLV`Ji&d!MWN=Aja2Q8$RM&_to3@RG z6kX9m$}OPN%l`^MlI!)RW89QWB$5uH=>?TUMk*mUwbp#qG0aTEno^;PxS*2I%rVr& z1doc5uBbjm33qSRly1tD$3r%Tby1JZl%tYTr9Ili2j?RlyUk?dAPrsQ=i{Kj@dE1{ z2VH^{cA0t=Ne)z}O?etRY1ZL`oWNNHXdMTg0_-*=bt54i^=d4MLO@X?h-j80D2SO@ zG-#&MnG`%qSW!t}!p_=rzR2U5qACG7Ocu0hlb@i93Osh!Ww+Cug-z1+dWE8>rlY8c zi}KxOk{^pKO9I3Y`FRLw)(RyW2W%3JkO*n)hmdnRTuuy~-TwKCSYcFw;oo&6Bt;|` z|Ip(_%mqXTqU}&}(N>4eLHqfDowh<}06QEU&r4~vQdK6W5`}xB8muIOh!973iB=kk zE-TGBY&?wuEKfT{E0S2zjwEa~DnD6FLZPgYZLUt8rRjKnY7~~KWc>(8G|q+FEE)xY zC39+8zDYj%6glNz6~YAcHdWFUT~h*j6;kn}Ajw@{3X!=Le+^aMp1mE4RzG$^+QGXZ zZ9_IE?F#Tb?GQP;-Opi;6K&n<)9hIq+Gxo7SeL-^0<@1KUCOBOJ7k~o6GJE(#H86W z<~!HZx9aR7n$~lxgV-lM9B1Ks_Jl5{8^kb1hwZ8}=Ef3CuJ!1~WG|Cbq{SP<^xK6z zq~A|CO7f(Vx6fy}m#YEjZz;;3UTK*=Kf6iK{YB|??-bs zAH7mpA6Z!1`OJs{1?<|D3HDK7OPToHdpooJ4~$*D`)aVfd%FI_rsqGLG2@HP&1aq( zGxO?Ju=MreVC9}+)Q&|Hva{-5nlxmzX|aLu>zKS_hfd$~VrU?=mw>qgOocNq>g$%% z*M3o}mGkZWlCuvzp7_+i!TR#igIRM!Yd}MBC1~4U-@1x9`_AZwgH97D@6VlV`k)~J zz~PKMt)$#HfCNJ1l+o`>_ zbL>xx*A4jX{$Mkj?JylLsGacDkk$L&TQTR8<8^Z^Z&e&?-u+|e;rVC8hHy4~C#Ru( z>GhW;)R&*E+kEs~`>e#N#~Lj^J3AK&mLYjdjkw_F(;a2|+S)r-C)PfGZ9~V>Mo0J3 z>cp@_$Jw<^bBEk;d4A)(fh}7HLfe5X5NLTcuVy3j#f3lH$ClnzIq|~Y^@Pz|0~@?~ zE1UiVpO@Zs>e<$gjG>N8wL4ENxZ3%7?eb+$n+ks_9v>eyw8cg>Au7-KKI#vKg4+&m zyVw_i8*LgMOaRaiWB>~MUjqs}Wa?GOjXZi2W`ddjAH$T=j6~6YrO~Ud-gNW=(DdRq ZQ*P~v5&nxa9tPwpDk#a{^u*jne*?+zWA6X} literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/forge/textures/gui/icons.png b/src/main/resources/assets/forge/textures/gui/icons.png new file mode 100644 index 0000000000000000000000000000000000000000..34e537c724d1adb9ec1e56d57c8ca829c2eac005 GIT binary patch literal 2455 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%0AX&AoP5Xk|0dTiQx1fPsq2 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 Date: Tue, 19 Aug 2025 21:28:21 +0800 Subject: [PATCH 02/91] Lang keys --- .../screen/CatalogueModListScreen.java | 30 +++++++++---------- .../resources/assets/forge/lang/en_us.lang | 16 ++++++++-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index e0793f619..69270b991 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -94,13 +94,13 @@ public void initGui() { int contentWidth = this.width - contentLeft - padding; int buttonWidth = (contentWidth - padding) / 3; - this.configButton = this.addButton(new CatalogueIconButton(3, contentLeft, 105, 10, 0, buttonWidth, I18n.format("catalogue.gui.config"))); + this.configButton = this.addButton(new CatalogueIconButton(3, contentLeft, 105, 10, 0, buttonWidth, I18n.format("fml.menu.mods.config"))); this.configButton.visible = false; - this.websiteButton = this.addButton(new CatalogueIconButton(4, contentLeft + buttonWidth + 5, 105, 20, 0, buttonWidth, I18n.format("catalogue.gui.website"))); + this.websiteButton = this.addButton(new CatalogueIconButton(4, contentLeft + buttonWidth + 5, 105, 20, 0, buttonWidth, I18n.format("fml.menu.mods.website"))); this.websiteButton.visible = false; - this.issueButton = this.addButton(new CatalogueIconButton(5, contentLeft + buttonWidth + buttonWidth + 10, 105, 30, 0, buttonWidth, I18n.format("catalogue.gui.issue"))); + this.issueButton = this.addButton(new CatalogueIconButton(5, contentLeft + buttonWidth + buttonWidth + 10, 105, 30, 0, buttonWidth, I18n.format("fml.menu.mods.issue"))); this.issueButton.visible = false; this.descriptionList = new StringList(contentWidth, this.height - 135 - 55, contentLeft, 130); @@ -177,7 +177,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { } if (ScreenUtil.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("catalogue.gui.info")); + this.setActiveTooltip(I18n.format("fml.menu.mods.info")); this.tooltipYOffset = 10; } @@ -498,7 +498,7 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti // Version check button if (this.selectedModInfo != null) { int contentLeft = this.modList.right + 12 + 10; - String version = I18n.format("catalogue.gui.version", this.selectedModInfo.getDisplayVersion()); + String version = I18n.format("fml.menu.mods.info.version", this.selectedModInfo.getDisplayVersion()); int versionWidth = this.fontRenderer.getStringWidth(version); if (ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { ForgeVersion.CheckResult result = ForgeVersion.getResult(this.selectedModInfo); @@ -558,11 +558,11 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { GlStateManager.disableBlend(); this.modList.drawScreen(mouseX, mouseY, partialTicks); - drawString(this.fontRenderer, TextFormatting.BOLD + I18n.format("catalogue.gui.title"), 70, 10, 0xFFFFFF); + drawString(this.fontRenderer, TextFormatting.BOLD + I18n.format("fml.menu.mods.title"), 70, 10, 0xFFFFFF); this.searchTextField.drawTextBox(); if(ScreenUtil.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("catalogue.gui.filter_updates")); + this.setActiveTooltip(I18n.format("fml.menu.mods.filterupdates")); this.tooltipYOffset = 10; } } @@ -594,7 +594,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { GlStateManager.popMatrix(); // Draw version - String modId = TextFormatting.DARK_GRAY + I18n.format("catalogue.gui.modid", this.selectedModInfo.getModId()); + String modId = TextFormatting.DARK_GRAY + I18n.format("fml.menu.mods.info.modid", this.selectedModInfo.getModId()); int modIdWidth = this.fontRenderer.getStringWidth(modId); drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); @@ -610,10 +610,10 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { // Draw version String displayVersion = this.selectedModInfo.getDisplayVersion(); - this.drawStringWithLabel("catalogue.gui.version", displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + this.drawStringWithLabel("fml.menu.mods.info.version", displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); // Draw inner version if the display version is different from it - int versionWidth = this.fontRenderer.getStringWidth(I18n.format("catalogue.gui.version", displayVersion)); + int versionWidth = this.fontRenderer.getStringWidth(I18n.format("fml.menu.mods.info.version", displayVersion)); String innerVersion = this.selectedModInfo.getVersion(); if (!displayVersion.equals(innerVersion) && ScreenUtil.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { this.setActiveTooltip(innerVersion); @@ -627,7 +627,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { int vOffset = result.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; ScreenUtil.blit(contentLeft + versionWidth + 5, 92, result.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); if(ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("catalogue.gui.update_available", result.url)); + this.setActiveTooltip(I18n.format("fml.menu.mods.info.updateavailable", result.url)); } } @@ -644,18 +644,18 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { // Draw credits String credits = metadata.credits; if(!credits.isEmpty()) { - this.drawStringWithLabel("catalogue.gui.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + this.drawStringWithLabel("fml.menu.mods.info.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); labelOffset -= 15; } // Draw authors String authors = metadata.getAuthorList(); if(!authors.isEmpty()) { - this.drawStringWithLabel("catalogue.gui.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + this.drawStringWithLabel("fml.menu.mods.info.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); } } } else { - String message = TextFormatting.GRAY + I18n.format("catalogue.gui.no_selection"); + String message = TextFormatting.GRAY + I18n.format("fml.menu.mods.noselection"); drawCenteredString(this.fontRenderer, message, contentLeft + contentWidth / 2, this.height / 2 - 5, 0xFFFFFF); } } @@ -902,7 +902,7 @@ private void updateSelectedModList() { private void updateSearchField(String value) { if(value.isEmpty()) { - this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search")); + this.searchTextField.setSuggestion(I18n.format("fml.menu.mods.search")); } else { Optional optional = Loader.instance().getActiveModList().stream().filter(info -> { return info.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); diff --git a/src/main/resources/assets/forge/lang/en_us.lang b/src/main/resources/assets/forge/lang/en_us.lang index af2b057c6..da557bd01 100644 --- a/src/main/resources/assets/forge/lang/en_us.lang +++ b/src/main/resources/assets/forge/lang/en_us.lang @@ -235,8 +235,20 @@ forge.controlsgui.control.mac=CMD + %s forge.controlsgui.alt=ALT + %s fml.menu.mods=Mods -fml.menu.mods.normal=Normal -fml.menu.mods.search=Search: +fml.menu.mods.title=Mods +fml.menu.mods.config=Config +fml.menu.mods.website=Website +fml.menu.mods.issue=Submit Bug +fml.menu.mods.search=Search... +fml.menu.mods.noselection=No mod selected... +fml.menu.mods.openmodsfolder=Open mods folder +fml.menu.mods.filterupdates=Show only mods with updates +fml.menu.mods.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! +fml.menu.mods.info.credits=Credits: %s +fml.menu.mods.info.authors=Authors: %s +fml.menu.mods.info.version=Version: %s +fml.menu.mods.info.modid=Mod ID: %s +fml.menu.mods.info.updateavailable=Update available: %s fml.menu.modoptions=Mod Options... item.forge.bucketFilled.name=%s Bucket From b72cf624768eeb84baa5d0472c8a370a20d204e2 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Mon, 25 Aug 2025 22:10:29 +0800 Subject: [PATCH 03/91] Sync changes --- .../minecraftforge/common/ForgeVersion.java | 5 + .../fml/client/TEMPmodlist/ScreenUtil.java | 24 +---- .../screen/CatalogueModListScreen.java | 100 ++++++++++-------- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/main/java/net/minecraftforge/common/ForgeVersion.java b/src/main/java/net/minecraftforge/common/ForgeVersion.java index 5a93552e0..85eb481e3 100644 --- a/src/main/java/net/minecraftforge/common/ForgeVersion.java +++ b/src/main/java/net/minecraftforge/common/ForgeVersion.java @@ -149,6 +149,11 @@ public boolean shouldDraw() return this != UP_TO_DATE; } + public boolean shouldUpdate() + { + return this == OUTDATED || this == BETA_OUTDATED; + } + public boolean isAnimated() { return animated; diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java index 6745ad654..07a710dd4 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java @@ -1,6 +1,7 @@ package net.minecraftforge.fml.client.TEMPmodlist; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.Tessellator; @@ -45,31 +46,12 @@ public Size2i(int width, int height) { } } - /** - * A backport of AbstractGUI.blit. Should not be overused. - */ public static void blit(int x, int y, int width, int height, float uOffset, float vOffset, int uWidth, int vHeight, int textureWidth, int textureHeight) { - float minU = uOffset / textureWidth; - float minV = vOffset / textureHeight; - float maxU = (uOffset + uWidth) / textureWidth; - float maxV = (vOffset + vHeight) / textureHeight; - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - buffer.pos(x, y + height, 0).tex(minU, maxV).endVertex(); - buffer.pos(x + width, y + height, 0).tex(maxU, maxV).endVertex(); - buffer.pos(x + width, y, 0).tex(maxU, minV).endVertex(); - buffer.pos(x, y, 0).tex(minU, minV).endVertex(); - tessellator.draw(); + Gui.drawScaledCustomSizeModalRect(x, y, uOffset, vOffset, uWidth, vHeight, width, height, textureWidth, textureHeight); } - /** - * A backport of AbstractGUI.blit. Should not be overused. - */ public static void blit(int x, int y, float uOffset, float vOffset, int width, int height, int textureWidth, int textureHeight) { - blit(x, y, width, height, uOffset, vOffset, width, height, textureWidth, textureHeight); + Gui.drawModalRectWithCustomSizedTexture(x, y, uOffset, vOffset, width, height, textureWidth, textureHeight); } } diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 69270b991..1a685bc36 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -50,7 +50,7 @@ public class CatalogueModListScreen extends GuiScreen { private static final Map> LOGO_CACHE = new HashMap<>(); private static final Map> ICON_CACHE = new HashMap<>(); private static final Map ITEM_CACHE = new HashMap<>(); - + private CatalogueTextField searchTextField; private ModList modList; private ModContainer selectedModInfo; @@ -112,11 +112,11 @@ public void initGui() { this.modList.filterAndUpdateList(this.searchTextField.getText()); // Resizing window causes all widgets to be recreated, therefore need to update selected info - if(this.selectedModInfo != null) { + if (this.selectedModInfo != null) { this.setSelectedModInfo(this.selectedModInfo); this.updateSelectedModList(); ModEntry entry = this.modList.getEntryFromInfo(this.selectedModInfo); - if(entry != null) { + if (entry != null) { this.modList.centerScrollOn(entry); } } @@ -125,7 +125,7 @@ public void initGui() { @Override public void actionPerformed(GuiButton button) { - switch(button.id) { + switch (button.id) { case 1: mc.displayGuiScreen(mainMenu); break; @@ -227,11 +227,11 @@ protected void elementClicked(int slotIndex, boolean isDoubleClick, int mouseX, public void filterAndUpdateList(String text) { List entries = Loader.instance().getActiveModList().stream() - .filter(info -> info.getName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) - .filter(info -> !updatesButton.selected() || ForgeVersion.getResult(info).status.shouldDraw()) - .map(info -> new ModEntry(info, this)) - .sorted(Comparator.comparing(entry -> entry.info.getName())) - .collect(Collectors.toList()); + .filter(info -> info.getName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) + .filter(info -> !updatesButton.selected() || ForgeVersion.getResult(info).status.shouldDraw()) + .map(info -> new ModEntry(info, this)) + .sorted(Comparator.comparing(entry -> entry.info.getName())) + .collect(Collectors.toList()); this.entries = entries; this.selectMod(this.getEntryFromInfo(selectedModInfo)); this.setAmountScrolled(0); @@ -303,12 +303,12 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, CatalogueModListScreen.this.loadAndCacheIcon(this.info); // Draw icon - if(ICON_CACHE.containsKey(this.info.getModId()) && ICON_CACHE.get(this.info.getModId()).getLeft() != null) { + if (ICON_CACHE.containsKey(this.info.getModId()) && ICON_CACHE.get(this.info.getModId()).getLeft() != null) { ResourceLocation logoResource = TextureMap.LOCATION_MISSING_TEXTURE; Size2i size = new Size2i(16, 16); Pair logoInfo = ICON_CACHE.get(this.info.getModId()); - if(logoInfo != null && logoInfo.getLeft() != null) { + if (logoInfo != null && logoInfo.getLeft() != null) { logoResource = logoInfo.getLeft(); size = logoInfo.getRight(); } @@ -325,14 +325,14 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, CatalogueModListScreen.this.mc.getRenderItem().renderItemIntoGUI(this.getItemIcon(), left + 4, top + 2); GlStateManager.disableDepth(); RenderHelper.disableStandardItemLighting(); - } catch(Exception e) { + } catch (Exception e) { ITEM_CACHE.put(this.info.getModId(), new ItemStack(Blocks.GRASS)); } } // Draws an icon if there is an update for the mod ForgeVersion.CheckResult result = ForgeVersion.getResult(this.info); - if(result.status.shouldDraw()) { + if (result.status.shouldDraw()) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); int vOffset = result.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; @@ -347,7 +347,7 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEven } private ItemStack getItemIcon() { - if(ITEM_CACHE.containsKey(this.info.getModId())) { + if (ITEM_CACHE.containsKey(this.info.getModId())) { return ITEM_CACHE.get(this.info.getModId()); } @@ -355,7 +355,7 @@ private ItemStack getItemIcon() { ITEM_CACHE.put(this.info.getModId(), new ItemStack(Blocks.GRASS)); // Special case for Forge to set item icon to anvil - if(this.info.getModId().equals("forge")) { + if (this.info.getModId().equals("forge")) { ItemStack anvil = new ItemStack(Blocks.ANVIL); ITEM_CACHE.put("forge", anvil); return anvil; @@ -378,9 +378,16 @@ private ItemStack getItemIcon() { // If the mod doesn't specify an item to use, Catalogue will attempt to get an item from the mod Optional optional = ForgeRegistries.ITEMS.getValuesCollection().stream().filter(item -> item.getRegistryName().getNamespace().equals(this.info.getModId())).map(ItemStack::new).findFirst(); - if(optional.isPresent()) { + if (optional.isPresent()) { ItemStack item = optional.get(); - if(!item.isEmpty()) { + if (!item.isEmpty()) { + // If the item is in a creative tab, Catalogue will use the tab's icon + if (item.getItem().getCreativeTab() != null) { + ItemStack tabItem = item.getItem().getCreativeTab().getIcon(); + if (tabItem != null && !tabItem.isEmpty() && tabItem.getItem().getRegistryName().getNamespace().equals(this.info.getModId())) { + item = tabItem; + } + } ITEM_CACHE.put(this.info.getModId(), item); return item; } @@ -392,20 +399,22 @@ private ItemStack getItemIcon() { private String getFormattedModName() { String name = this.info.getName(); int width = this.list.getListWidth() - (this.list.getMaxScroll() > 0 ? 30 : 24); - if(CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > width) { + if (CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > width) { name = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(name, width - 10) + "..."; } - if(this.info.getModId().equals("forge") || this.info.getModId().equals("minecraft")) { + if (this.info.getModId().equals("forge") || this.info.getModId().equals("minecraft")) { return TextFormatting.DARK_GRAY + name; } return name; } @Override - public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) {} + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { + } @Override - public void updatePosition(int slotIndex, int x, int y, float partialTicks) {} + public void updatePosition(int slotIndex, int x, int y, float partialTicks) { + } } private class StringList extends CatalogueListExtended { @@ -473,10 +482,12 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEven } @Override - public void updatePosition(int slotIndex, int x, int y, float partialTicks) {} + public void updatePosition(int slotIndex, int x, int y, float partialTicks) { + } @Override - public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) {} + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { + } } @@ -582,7 +593,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { int contentLeft = this.modList.right + 12 + 10; int contentWidth = this.width - contentLeft - 10; - if(this.selectedModInfo != null) { + if (this.selectedModInfo != null) { // Draw mod logo this.drawLogo(contentWidth, contentLeft, 10, this.width - (this.modList.right + 12 + 10) - 10, 50); @@ -665,25 +676,25 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { * specified max width, it will automatically be trimmed and allows the user to hover the * string with their mouse to read the full contents. * - * @param format a string to prepend to the content - * @param text the string to render - * @param x the x position - * @param y the y position - * @param maxWidth the maximum width the string can render - * @param mouseX the current mouse x position - * @param mouseY the current mouse u position + * @param format a string to prepend to the content + * @param text the string to render + * @param x the x position + * @param y the y position + * @param maxWidth the maximum width the string can render + * @param mouseX the current mouse x position + * @param mouseY the current mouse u position */ @SuppressWarnings("SameParameterValue") private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { String formatted = I18n.format(format, text); // Attempting to keep Forge's lang since it's already support many languages String label = formatted.substring(0, formatted.indexOf(":") + 1); String content = formatted.substring(formatted.indexOf(":") + 1); - if(this.fontRenderer.getStringWidth(formatted) > maxWidth) { + if (this.fontRenderer.getStringWidth(formatted) > maxWidth) { content = this.fontRenderer.trimStringToWidth(content, maxWidth - this.fontRenderer.getStringWidth(label) - 7) + "..."; String credits = labelColor + label; credits += contentColor + content; drawString(this.fontRenderer, credits, x, y, 0xFFFFFF); - if(ScreenUtil.isMouseWithin(x, y, maxWidth, 9, mouseX, mouseY)) { // Sets the active tool tip if string is too long so users can still read it + if (ScreenUtil.isMouseWithin(x, y, maxWidth, 9, mouseX, mouseY)) { // Sets the active tool tip if string is too long so users can still read it this.setActiveTooltip(text); } } else { @@ -722,11 +733,12 @@ private void loadAndCacheLogo(ModContainer info) { if (logo == null) return; TextureManager textureManager = this.mc.getTextureManager(); LOGO_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("modlogo", this.createLogoTexture(logo, true)), new Size2i(logo.getWidth(), logo.getHeight()))); - } catch (IOException ignored) {} + } catch (IOException ignored) { + } } private void loadAndCacheIcon(ModContainer info) { - if(ICON_CACHE.containsKey(info.getModId())) + if (ICON_CACHE.containsKey(info.getModId())) return; // Fills an empty icon as icon may not be present @@ -752,7 +764,8 @@ private void loadAndCacheIcon(ModContainer info) { TextureManager textureManager = this.mc.getTextureManager(); ICON_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(icon, true)), new Size2i(icon.getWidth(), icon.getHeight()))); return; - } catch (IOException ignored) {} + } catch (IOException ignored) { + } } // Attempts to use the logo file if it's a square @@ -796,7 +809,8 @@ private void loadAndCacheIcon(ModContainer info) { ICON_CACHE.put(modId, Pair.of(textureId, size)); LOGO_CACHE.put(modId, Pair.of(textureId, size)); } - } catch (IOException ignored) {} + } catch (IOException ignored) { + } } private DynamicTexture createLogoTexture(BufferedImage image, boolean smooth) { @@ -810,13 +824,13 @@ public void updateDynamicTexture() { @SuppressWarnings("SameParameterValue") private void drawLogo(int contentWidth, int x, int y, int maxWidth, int maxHeight) { - if(this.selectedModInfo != null) { + if (this.selectedModInfo != null) { ResourceLocation logoResource = MISSING_BANNER; Size2i size = new Size2i(600, 120); - if(LOGO_CACHE.containsKey(this.selectedModInfo.getModId())) { + if (LOGO_CACHE.containsKey(this.selectedModInfo.getModId())) { Pair logoInfo = LOGO_CACHE.get(this.selectedModInfo.getModId()); - if(logoInfo.getLeft() != null) { + if (logoInfo.getLeft() != null) { logoResource = logoInfo.getLeft(); size = logoInfo.getRight(); } @@ -895,7 +909,7 @@ private int getLabelCount(ModContainer selectedModInfo) { private void updateSelectedModList() { ModEntry selectedEntry = this.modList.getEntryFromInfo(this.selectedModInfo); - if(selectedEntry != null) { + if (selectedEntry != null) { this.modList.selectMod(selectedEntry); } } @@ -907,7 +921,7 @@ private void updateSearchField(String value) { Optional optional = Loader.instance().getActiveModList().stream().filter(info -> { return info.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); }).min(Comparator.comparing(ModContainer::getName)); - if(optional.isPresent()) { + if (optional.isPresent()) { int length = value.length(); String displayName = optional.get().getName(); this.searchTextField.setSuggestion(displayName.substring(length)); @@ -921,7 +935,7 @@ private void updateSearchField(String value) { * Opens a link with a url defined in the mod's info */ private void openLink(@Nullable ModContainer configurable) { - if(configurable != null) { + if (configurable != null) { ModMetadata metadata = configurable.getMetadata(); // The config button is only enabled when checked, so it is unnecessary to check again. openLink(metadata.url); From 228df8f8365072ce9699683987597b32b5e18a02 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Mon, 25 Aug 2025 22:20:43 +0800 Subject: [PATCH 04/91] Adapt cleanroom version check --- .../minecraftforge/common/ForgeVersion.java | 5 -- .../screen/CatalogueModListScreen.java | 67 ++++++++++++++----- .../resources/assets/forge/lang/en_us.lang | 7 +- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/minecraftforge/common/ForgeVersion.java b/src/main/java/net/minecraftforge/common/ForgeVersion.java index 85eb481e3..5a93552e0 100644 --- a/src/main/java/net/minecraftforge/common/ForgeVersion.java +++ b/src/main/java/net/minecraftforge/common/ForgeVersion.java @@ -149,11 +149,6 @@ public boolean shouldDraw() return this != UP_TO_DATE; } - public boolean shouldUpdate() - { - return this == OUTDATED || this == BETA_OUTDATED; - } - public boolean isAnimated() { return animated; diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 1a685bc36..436d7d7b2 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -228,7 +228,7 @@ protected void elementClicked(int slotIndex, boolean isDoubleClick, int mouseX, public void filterAndUpdateList(String text) { List entries = Loader.instance().getActiveModList().stream() .filter(info -> info.getName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) - .filter(info -> !updatesButton.selected() || ForgeVersion.getResult(info).status.shouldDraw()) + .filter(info -> !updatesButton.selected() || shouldUpdate(ForgeVersion.getCleanResult(info))) .map(info -> new ModEntry(info, this)) .sorted(Comparator.comparing(entry -> entry.info.getName())) .collect(Collectors.toList()); @@ -331,8 +331,8 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, } // Draws an icon if there is an update for the mod - ForgeVersion.CheckResult result = ForgeVersion.getResult(this.info); - if (result.status.shouldDraw()) { + ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.info); + if (shouldDraw(result)) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); int vOffset = result.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; @@ -365,7 +365,7 @@ private ItemStack getItemIcon() { // // Gets the raw item icon resource string // String itemIcon = this.info.getCustomModProperties().get("catalogueItemIcon"); // -// if(!itemIcon.isEmpty()) { +// if (!itemIcon.isEmpty()) { // try { // String[] parts = itemIcon.split(":"); // Item item = Item.getByNameOrId(parts[0]); @@ -512,9 +512,9 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti String version = I18n.format("fml.menu.mods.info.version", this.selectedModInfo.getDisplayVersion()); int versionWidth = this.fontRenderer.getStringWidth(version); if (ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { - ForgeVersion.CheckResult result = ForgeVersion.getResult(this.selectedModInfo); - if (result.status.shouldDraw() && result.url != null) { - this.openLink(result.url); + ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.selectedModInfo); + if (shouldUpdate(result) && result.homepage != null) { + this.openLink(result.homepage); } } } @@ -572,7 +572,7 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { drawString(this.fontRenderer, TextFormatting.BOLD + I18n.format("fml.menu.mods.title"), 70, 10, 0xFFFFFF); this.searchTextField.drawTextBox(); - if(ScreenUtil.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { + if (ScreenUtil.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { this.setActiveTooltip(I18n.format("fml.menu.mods.filterupdates")); this.tooltipYOffset = 10; } @@ -610,8 +610,8 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); // // Set tooltip for secure mod features forge has -// if(ScreenUtil.isMouseWithin(contentLeft + contentWidth - modIdWidth, 92, modIdWidth, this.font.lineHeight, mouseX, mouseY)) { -// if(FMLEnvironment.secureJarsEnabled) { +// if (ScreenUtil.isMouseWithin(contentLeft + contentWidth - modIdWidth, 92, modIdWidth, this.font.lineHeight, mouseX, mouseY)) { +// if (FMLEnvironment.secureJarsEnabled) { // this.setActiveTooltip(ForgeI18n.parseMessage("fml.menu.mods.info.signature", ((ModInfo) this.selectedModInfo).getOwningFile().getCodeSigningFingerprint().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.signature.unsigned")))); // this.setActiveTooltip(ForgeI18n.parseMessage("fml.menu.mods.info.trust", ((ModInfo) this.selectedModInfo).getOwningFile().getTrustData().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.trust.noauthority")))); // } else { @@ -631,14 +631,35 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { } // Draws an icon if there is an update for the mod - ForgeVersion.CheckResult result = ForgeVersion.getResult(this.selectedModInfo); - if(result.status.shouldDraw() && result.url != null) { + ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.selectedModInfo); + if (shouldDraw(result) && result.url != null) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); int vOffset = result.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; ScreenUtil.blit(contentLeft + versionWidth + 5, 92, result.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); - if(ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("fml.menu.mods.info.updateavailable", result.url)); + if (ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { + switch (result.status) { + case BETA: + this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.beta")); + break; + case AHEAD: + this.setActiveTooltip(TextFormatting.LIGHT_PURPLE + I18n.format("fml.menu.mods.info.ahead", result.latestFound)); + break; + case BETA_OUTDATED: + if (result.homepage != null) { + this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.betaupdateavailable", result.latestFound, result.homepage)); + } else { + this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.betaupdateavailablenopage", result.latestFound)); + } + break; + case OUTDATED: + if (result.homepage != null) { + this.setActiveTooltip(TextFormatting.GREEN + I18n.format("fml.menu.mods.info.updateavailable", result.latestFound, result.homepage)); + } else { + this.setActiveTooltip(TextFormatting.GREEN + I18n.format("fml.menu.mods.info.updateavailablenopage", result.latestFound)); + } + break; + } } } @@ -654,14 +675,14 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { // Draw credits String credits = metadata.credits; - if(!credits.isEmpty()) { + if (!credits.isEmpty()) { this.drawStringWithLabel("fml.menu.mods.info.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); labelOffset -= 15; } // Draw authors String authors = metadata.getAuthorList(); - if(!authors.isEmpty()) { + if (!authors.isEmpty()) { this.drawStringWithLabel("fml.menu.mods.info.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); } } @@ -715,7 +736,7 @@ private void loadAndCacheLogo(ModContainer info) { String s = metadata.logoFile; if (s.isEmpty()) return; -// if(s.contains("/") || s.contains("\\")) { +// if (s.contains("/") || s.contains("\\")) { // Catalogue.LOGGER.warn("Skipped loading logo file from {}. The file name '{}' contained illegal characters '/' or '\\'", info.getName(), s); // return; // } @@ -915,7 +936,7 @@ private void updateSelectedModList() { } private void updateSearchField(String value) { - if(value.isEmpty()) { + if (value.isEmpty()) { this.searchTextField.setSuggestion(I18n.format("fml.menu.mods.search")); } else { Optional optional = Loader.instance().getActiveModList().stream().filter(info -> { @@ -946,4 +967,14 @@ private void openLink(String url) { Style style = new Style().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)); this.handleComponentClick(new TextComponentString("").setStyle(style)); } + + private boolean shouldDraw(ForgeVersion.CheckResult result) { + return result != null && result.status.shouldDraw(); + } + + private boolean shouldUpdate(ForgeVersion.CheckResult result) { + if (result == null) return false; + ForgeVersion.Status status = result.status; + return status == ForgeVersion.Status.OUTDATED || status == ForgeVersion.Status.BETA_OUTDATED; + } } diff --git a/src/main/resources/assets/forge/lang/en_us.lang b/src/main/resources/assets/forge/lang/en_us.lang index da557bd01..eaeae5597 100644 --- a/src/main/resources/assets/forge/lang/en_us.lang +++ b/src/main/resources/assets/forge/lang/en_us.lang @@ -248,7 +248,12 @@ fml.menu.mods.info.credits=Credits: %s fml.menu.mods.info.authors=Authors: %s fml.menu.mods.info.version=Version: %s fml.menu.mods.info.modid=Mod ID: %s -fml.menu.mods.info.updateavailable=Update available: %s +fml.menu.mods.info.beta=This version is a beta version +fml.menu.mods.info.ahead=This version is ahead of the latest version found (%s) +fml.menu.mods.info.updateavailable=Update (%s) Available: %s +fml.menu.mods.info.updateavailablenopage=Update (%s) Available +fml.menu.mods.info.betaupdateavailable=Beta Update (%s) Available: %s +fml.menu.mods.info.betaupdateavailablenopage=Beta Update (%s) Available fml.menu.modoptions=Mod Options... item.forge.bucketFilled.name=%s Bucket From 2f543660e36b840401652d235c684dbb926c1387 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:18:37 +0800 Subject: [PATCH 05/91] Change search lang key --- .../fml/client/TEMPmodlist/screen/CatalogueModListScreen.java | 2 +- src/main/resources/assets/forge/lang/en_us.lang | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 436d7d7b2..8e50cc520 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -937,7 +937,7 @@ private void updateSelectedModList() { private void updateSearchField(String value) { if (value.isEmpty()) { - this.searchTextField.setSuggestion(I18n.format("fml.menu.mods.search")); + this.searchTextField.setSuggestion(I18n.format("fml.menu.mods.searchwithdots")); } else { Optional optional = Loader.instance().getActiveModList().stream().filter(info -> { return info.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); diff --git a/src/main/resources/assets/forge/lang/en_us.lang b/src/main/resources/assets/forge/lang/en_us.lang index eaeae5597..b5294ff51 100644 --- a/src/main/resources/assets/forge/lang/en_us.lang +++ b/src/main/resources/assets/forge/lang/en_us.lang @@ -239,7 +239,7 @@ fml.menu.mods.title=Mods fml.menu.mods.config=Config fml.menu.mods.website=Website fml.menu.mods.issue=Submit Bug -fml.menu.mods.search=Search... +fml.menu.mods.searchwithdots=Search... fml.menu.mods.noselection=No mod selected... fml.menu.mods.openmodsfolder=Open mods folder fml.menu.mods.filterupdates=Show only mods with updates From 4bb77d947713a7e18aaded1a549666ecc365c9f1 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:46:28 +0800 Subject: [PATCH 06/91] More metadata --- .../screen/CatalogueModListScreen.java | 178 +++++++++--------- .../fml/common/ModMetadata.java | 4 + .../resources/assets/forge/lang/en_us.lang | 5 +- 3 files changed, 91 insertions(+), 96 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 8e50cc520..6f377907d 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -13,6 +13,7 @@ import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.IResourcePack; import net.minecraft.init.Blocks; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.Style; @@ -146,10 +147,10 @@ public void actionPerformed(GuiButton button) { } break; case 4: - this.openLink(this.selectedModInfo); + this.openLink(0, this.selectedModInfo); break; case 5: - //WIP + this.openLink(1, this.selectedModInfo); break; case 6: this.modList.filterAndUpdateList(this.searchTextField.getText()); @@ -299,7 +300,7 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, drawString(fontRenderer, this.getFormattedModName(), left + 24, top + 2, 0xFFFFFF); drawString(fontRenderer, TextFormatting.GRAY + this.info.getDisplayVersion(), left + 24, top + 12, 0xFFFFFF); - //WIP + // WIP CatalogueModListScreen.this.loadAndCacheIcon(this.info); // Draw icon @@ -361,20 +362,24 @@ private ItemStack getItemIcon() { return anvil; } - // Not working -// // Gets the raw item icon resource string -// String itemIcon = this.info.getCustomModProperties().get("catalogueItemIcon"); -// -// if (!itemIcon.isEmpty()) { -// try { -// String[] parts = itemIcon.split(":"); -// Item item = Item.getByNameOrId(parts[0]); -// int meta = parts.length > 1 ? Integer.parseInt(parts[1]) : 0; -// ItemStack itemStack = new ItemStack(item, 1, meta); -// ITEM_CACHE.put(this.info.getModId(), itemStack); -// return itemStack; -// } catch(Exception ignored) {} -// } + // Gets the raw item icon resource string + ModMetadata metadata = this.info.getMetadata(); + if (metadata != null && !metadata.autogenerated) { + String itemIcon = metadata.iconItem; + if (!itemIcon.isEmpty()) { + try { + // 0:mod id 1:item name (2:metadata) + String[] parts = itemIcon.split(":"); + Item item = Item.getByNameOrId(parts[0] + ":" + parts[1]); + if (item != null) { + int meta = parts.length > 2 ? Integer.parseInt(parts[2]) : 0; + ItemStack itemStack = new ItemStack(item, 1, meta); + ITEM_CACHE.put(this.info.getModId(), itemStack); + return itemStack; + } + } catch (Exception ignored) {} + } + } // If the mod doesn't specify an item to use, Catalogue will attempt to get an item from the mod Optional optional = ForgeRegistries.ITEMS.getValuesCollection().stream().filter(item -> item.getRegistryName().getNamespace().equals(this.info.getModId())).map(ItemStack::new).findFirst(); @@ -668,10 +673,12 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { int labelOffset = this.height - 20; -// // Draw license -// String license = this.selectedModInfo.getMetadata().getLicense(); -// this.drawStringWithLabel("fml.menu.mods.info.license", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); -// labelOffset -= 15; + // Draw license + String license = metadata.license; + if (!license.isEmpty()) { + this.drawStringWithLabel("fml.menu.mods.info.license", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + labelOffset -= 15; + } // Draw credits String credits = metadata.credits; @@ -730,17 +737,11 @@ private void loadAndCacheLogo(ModContainer info) { LOGO_CACHE.put(info.getModId(), Pair.of(null, new Size2i(0, 0))); // Attempts to load the real logo - ModContainer modInfo = (ModContainer) info; - ModMetadata metadata = modInfo.getMetadata(); + ModMetadata metadata = info.getMetadata(); if (metadata == null) return; String s = metadata.logoFile; if (s.isEmpty()) return; -// if (s.contains("/") || s.contains("\\")) { -// Catalogue.LOGGER.warn("Skipped loading logo file from {}. The file name '{}' contained illegal characters '/' or '\\'", info.getName(), s); -// return; -// } - IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); BufferedImage logo = null; try { @@ -759,78 +760,63 @@ private void loadAndCacheLogo(ModContainer info) { } private void loadAndCacheIcon(ModContainer info) { - if (ICON_CACHE.containsKey(info.getModId())) - return; + if (ICON_CACHE.containsKey(info.getModId())) return; // Fills an empty icon as icon may not be present ICON_CACHE.put(info.getModId(), Pair.of(null, new Size2i(0, 0))); - // Not working - // Attempts to load the real icon - ModContainer modInfo = (ModContainer) info; - if (modInfo.getCustomModProperties().containsKey("catalogueImageIcon")) { - String s = (String) modInfo.getCustomModProperties().get("catalogueImageIcon"); - - if (s.isEmpty()) return; - -// if (s.contains("/") || s.contains("\\")) { -// Catalogue.LOGGER.warn("Skipped loading Catalogue icon file from {}. The file name '{}' contained illegal characters '/' or '\\'", info.getName(), s); -// return; -// } + ModMetadata metadata = info.getMetadata(); + if (metadata == null) return; - try (InputStream is = getClass().getResourceAsStream(s)) { - BufferedImage icon = null; + // Attempts to load the real icon + String iconString = metadata.iconFile; + if (!iconString.isEmpty()) { + BufferedImage icon = null; + try (InputStream is = getClass().getResourceAsStream(iconString)) { if (is != null) icon = TextureUtil.readBufferedImage(is); - if (icon == null) return; - TextureManager textureManager = this.mc.getTextureManager(); - ICON_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(icon, true)), new Size2i(icon.getWidth(), icon.getHeight()))); - return; + if (icon != null) { + TextureManager textureManager = this.mc.getTextureManager(); + ICON_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(icon, false)), new Size2i(icon.getWidth(), icon.getHeight()))); + return; + } } catch (IOException ignored) { } } // Attempts to use the logo file if it's a square - ModMetadata metadata = modInfo.getMetadata(); - if (metadata == null) return; - String s = metadata.logoFile; - if (s.isEmpty()) return; - -// if (s.contains("/") || s.contains("\\")) { -// Catalogue.LOGGER.warn("Skipped loading logo file from {}. The file name '{}' contained illegal characters '/' or '\\'", info.getName(), s); -// return; -// } - - IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); - BufferedImage logo = null; - try { - if (resourcePack != null) { - logo = resourcePack.getPackImage(); - } else { - InputStream is = getClass().getResourceAsStream(s); - if (is != null) logo = TextureUtil.readBufferedImage(is); - } - if (logo == null) return; - if (logo.getWidth() == logo.getHeight()) { - TextureManager textureManager = this.mc.getTextureManager(); - String modId = info.getModId(); - - /* The first selected mod will have its logo cached before the icon, so we - * can just use the logo instead of loading the image again. */ - if (LOGO_CACHE.containsKey(modId)) { - if (LOGO_CACHE.get(modId).getLeft() != null) { - ICON_CACHE.put(modId, LOGO_CACHE.get(modId)); - return; - } + String logoString = metadata.logoFile; + if (!logoString.isEmpty()) { + IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); + BufferedImage logo = null; + try { + if (resourcePack != null) { + logo = resourcePack.getPackImage(); + } else { + InputStream is = getClass().getResourceAsStream(logoString); + if (is != null) logo = TextureUtil.readBufferedImage(is); } + if (logo != null && logo.getWidth() == logo.getHeight()) { + TextureManager textureManager = this.mc.getTextureManager(); + String modId = info.getModId(); + + /* The first selected mod will have its logo cached before the icon, so we + * can just use the logo instead of loading the image again. */ + if (LOGO_CACHE.containsKey(modId)) { + if (LOGO_CACHE.get(modId).getLeft() != null) { + ICON_CACHE.put(modId, LOGO_CACHE.get(modId)); + return; + } + } - /* Since the icon will be same as the logo, we can cache into both icon and logo cache */ - DynamicTexture texture = this.createLogoTexture(logo, true); - Size2i size = new Size2i(logo.getWidth(), logo.getHeight()); - ResourceLocation textureId = textureManager.getDynamicTextureLocation("catalogueicon", texture); - ICON_CACHE.put(modId, Pair.of(textureId, size)); - LOGO_CACHE.put(modId, Pair.of(textureId, size)); + /* Since the icon will be same as the logo, we can cache into both icon and logo cache */ + DynamicTexture texture = this.createLogoTexture(logo, true); + Size2i size = new Size2i(logo.getWidth(), logo.getHeight()); + ResourceLocation textureId = textureManager.getDynamicTextureLocation("catalogueicon", texture); + ICON_CACHE.put(modId, Pair.of(textureId, size)); + LOGO_CACHE.put(modId, Pair.of(textureId, size)); + } + } catch (IOException ignored) { } - } catch (IOException ignored) { } } @@ -902,11 +888,9 @@ private void setSelectedModInfo(ModContainer selectedModInfo) { ModMetadata metadata = selectedModInfo.getMetadata(); if (metadata != null && !metadata.autogenerated) { this.websiteButton.enabled = !metadata.url.isEmpty(); + this.issueButton.enabled = !metadata.issueTrackerUrl.isEmpty(); } - this.issueButton.enabled = false; -// this.issueButton.active = ((ModInfo) selectedModInfo).getOwningFile().getConfigElement("issueTrackerURL").isPresent(); - int contentLeft = this.modList.right + 12 + 10; int contentWidth = this.width - contentLeft - 10; int labelCount = this.getLabelCount(selectedModInfo); @@ -917,14 +901,13 @@ private void setSelectedModInfo(ModContainer selectedModInfo) { } private int getLabelCount(ModContainer selectedModInfo) { - int count = 1; //1 by default since license property will always exist - + int count = 0; ModMetadata metadata = selectedModInfo.getMetadata(); if (metadata != null && !metadata.autogenerated) { + if (!metadata.license.isEmpty()) count++; if (!metadata.credits.isEmpty()) count++; if (!metadata.authorList.isEmpty()) count++; } - return count; } @@ -955,11 +938,18 @@ private void updateSearchField(String value) { /** * Opens a link with a url defined in the mod's info */ - private void openLink(@Nullable ModContainer configurable) { + private void openLink(int key, @Nullable ModContainer configurable) { if (configurable != null) { ModMetadata metadata = configurable.getMetadata(); // The config button is only enabled when checked, so it is unnecessary to check again. - openLink(metadata.url); + switch (key) { + case 0: + this.openLink(metadata.url); + break; + case 1: + this.openLink(metadata.issueTrackerUrl); + break; + } } } diff --git a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java index 909423056..d6b842dd3 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java +++ b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java @@ -47,11 +47,15 @@ public class ModMetadata * URL to update json file. Format is defined here: https://gist.github.com/LexManos/7aacb9aa991330523884 */ public String updateJSON = ""; + public String issueTrackerUrl = ""; public String logoFile = ""; + public String iconFile = ""; + public String iconItem = ""; public String version = ""; public List authorList = Lists.newArrayList(); public String credits = ""; + public String license = ""; public String parent = ""; public String[] screenshots; diff --git a/src/main/resources/assets/forge/lang/en_us.lang b/src/main/resources/assets/forge/lang/en_us.lang index b5294ff51..62452d800 100644 --- a/src/main/resources/assets/forge/lang/en_us.lang +++ b/src/main/resources/assets/forge/lang/en_us.lang @@ -244,10 +244,11 @@ fml.menu.mods.noselection=No mod selected... fml.menu.mods.openmodsfolder=Open mods folder fml.menu.mods.filterupdates=Show only mods with updates fml.menu.mods.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! -fml.menu.mods.info.credits=Credits: %s -fml.menu.mods.info.authors=Authors: %s fml.menu.mods.info.version=Version: %s fml.menu.mods.info.modid=Mod ID: %s +fml.menu.mods.info.authors=Authors: %s +fml.menu.mods.info.credits=Credits: %s +fml.menu.mods.info.license=License: %s fml.menu.mods.info.beta=This version is a beta version fml.menu.mods.info.ahead=This version is ahead of the latest version found (%s) fml.menu.mods.info.updateavailable=Update (%s) Available: %s From fc61b533e14cebf6686280bd2d5ac20020c9e8f9 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:10:18 +0800 Subject: [PATCH 07/91] "/" is a problem --- .../TEMPmodlist/screen/CatalogueModListScreen.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 6f377907d..0092923a1 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -745,10 +745,12 @@ private void loadAndCacheLogo(ModContainer info) { IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); BufferedImage logo = null; try { - // Amazing. Forge's banner cannot be loaded though its resource pack is not null. - if (resourcePack != null && !info.getModId().equals("forge")) { + if (resourcePack != null && !s.startsWith("/")) { logo = resourcePack.getPackImage(); } else { + if (!s.startsWith("/")) { + s = "/" + s; + } InputStream is = getClass().getResourceAsStream(s); if (is != null) logo = TextureUtil.readBufferedImage(is); } @@ -771,6 +773,9 @@ private void loadAndCacheIcon(ModContainer info) { // Attempts to load the real icon String iconString = metadata.iconFile; if (!iconString.isEmpty()) { + if (!iconString.startsWith("/")) { + iconString = "/" + iconString; + } BufferedImage icon = null; try (InputStream is = getClass().getResourceAsStream(iconString)) { if (is != null) icon = TextureUtil.readBufferedImage(is); @@ -789,9 +794,12 @@ private void loadAndCacheIcon(ModContainer info) { IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); BufferedImage logo = null; try { - if (resourcePack != null) { + if (resourcePack != null && !logoString.startsWith("/")) { logo = resourcePack.getPackImage(); } else { + if (!logoString.startsWith("/")) { + logoString = "/" + logoString; + } InputStream is = getClass().getResourceAsStream(logoString); if (is != null) logo = TextureUtil.readBufferedImage(is); } From a0504da1e92ef0a567044f0998546985a5e4ec8a Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:00:54 +0800 Subject: [PATCH 08/91] Minecraft should be a grass block --- .../fml/client/TEMPmodlist/screen/CatalogueModListScreen.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 0092923a1..3e0395199 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -355,6 +355,9 @@ private ItemStack getItemIcon() { // Put grass as default item icon ITEM_CACHE.put(this.info.getModId(), new ItemStack(Blocks.GRASS)); + // Minecraft is a grass block + if (this.info.getModId().equals("minecraft")) return new ItemStack(Blocks.GRASS); + // Special case for Forge to set item icon to anvil if (this.info.getModId().equals("forge")) { ItemStack anvil = new ItemStack(Blocks.ANVIL); From b56b4faabaabb81812099dddc2119fcdffb262d5 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:25:58 +0800 Subject: [PATCH 09/91] Logos Unnecessary at all. But it is cool! --- .../cleanroommc/common/CleanroomContainer.java | 4 ++-- .../common/ConfigAnytimeContainer.java | 1 + .../com/cleanroommc/common/MixinContainer.java | 1 + .../screen/CatalogueModListScreen.java | 2 +- src/main/resources/cleanroom_icon.png | Bin 0 -> 40127 bytes src/main/resources/configanytime_icon.png | Bin 0 -> 20138 bytes src/main/resources/mixinbooter_icon.png | Bin 0 -> 653761 bytes 7 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/cleanroom_icon.png create mode 100644 src/main/resources/configanytime_icon.png create mode 100644 src/main/resources/mixinbooter_icon.png diff --git a/src/main/java/com/cleanroommc/common/CleanroomContainer.java b/src/main/java/com/cleanroommc/common/CleanroomContainer.java index 94ba529b7..4a7f9db9d 100644 --- a/src/main/java/com/cleanroommc/common/CleanroomContainer.java +++ b/src/main/java/com/cleanroommc/common/CleanroomContainer.java @@ -15,11 +15,11 @@ public CleanroomContainer() { meta.name = "Cleanroom"; meta.description = """ Cleanroom is a 1.12.2 Forge fork, providing newer toolchain, new APIs and 99% compatibility. - Our plan is to make 1.12.2 up-to-date with latest toolchain while adding more feature to - Forge & vanilla, so we don't need to care about higher versions' X point release * Y Mod API headache. + Our plan is to make 1.12.2 up-to-date with latest toolchain while adding more feature to Forge & vanilla, so we don't need to care about higher versions' X point release * Y Mod API headache. """; meta.version = CleanroomVersion.VERSION; meta.authorList = Arrays.asList("LexManos", "cpw", "fry", "Rongmario", "kappa_maintainer", "Li"); + meta.logoFile = "/cleanroom_icon.png"; } @Override diff --git a/src/main/java/com/cleanroommc/common/ConfigAnytimeContainer.java b/src/main/java/com/cleanroommc/common/ConfigAnytimeContainer.java index eda4f2bdc..fc182736e 100644 --- a/src/main/java/com/cleanroommc/common/ConfigAnytimeContainer.java +++ b/src/main/java/com/cleanroommc/common/ConfigAnytimeContainer.java @@ -17,6 +17,7 @@ public ConfigAnytimeContainer() { meta.description = "Allows Forge configurations to be setup at any point in time."; meta.version = ForgeEarlyConfig.CUSTOM_BUILT_IN_MOD_VERSION ? ForgeEarlyConfig.CONFIG_ANY_TIME_VERSION : "3.0"; meta.authorList.add("Rongmario"); + meta.logoFile = "/configanytime_icon.png"; } @Override diff --git a/src/main/java/com/cleanroommc/common/MixinContainer.java b/src/main/java/com/cleanroommc/common/MixinContainer.java index 2cf0bdb5c..fd9235073 100644 --- a/src/main/java/com/cleanroommc/common/MixinContainer.java +++ b/src/main/java/com/cleanroommc/common/MixinContainer.java @@ -15,6 +15,7 @@ public MixinContainer() { meta.description = "A Mixin library and loader."; meta.version = ForgeEarlyConfig.CUSTOM_BUILT_IN_MOD_VERSION ? ForgeEarlyConfig.MIXIN_BOOTER_VERSION : "10.6"; meta.authorList.add("Rongmario"); + meta.logoFile = "/mixinbooter_icon.png"; } @Override diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 3e0395199..e3d55e93b 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -410,7 +410,7 @@ private String getFormattedModName() { if (CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > width) { name = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(name, width - 10) + "..."; } - if (this.info.getModId().equals("forge") || this.info.getModId().equals("minecraft")) { + if (this.info.getModId().equals("forge") || this.info.getModId().equals("minecraft") || this.info.getModId().equals("cleanroom")) { return TextFormatting.DARK_GRAY + name; } return name; diff --git a/src/main/resources/cleanroom_icon.png b/src/main/resources/cleanroom_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..39754085d0bcabfb48a03624f85b02cd45664b2f GIT binary patch literal 40127 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%0A^F9V}B7^{y_xC7{ReSB~pGw!- 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î`yye8kFW0{C;z*jJ}*YvBms|`uj)MT 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/configanytime_icon.png b/src/main/resources/configanytime_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4509a0471d1d6016b60491588ec4f92f12619b09 GIT binary patch literal 20138 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 Z&j6n^TR{o)iCF*u002ovPDHLkV1f%=ay$S4 literal 0 HcmV?d00001 diff --git a/src/main/resources/mixinbooter_icon.png b/src/main/resources/mixinbooter_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..014608ef475659e23308eac7950ec6805e4de0db GIT binary patch literal 653761 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℘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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/forge/textures/gui/missing_background.png b/src/main/resources/assets/forge/textures/gui/missing_background.png new file mode 100644 index 0000000000000000000000000000000000000000..acf473c6b89a61090707a19d00d03c56ca9e3edf GIT binary patch literal 67119 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 qNH|!XIzRvCVAh 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


~kx&s(6^E>&&=N9L`9DK9#r9?R+(rzu&t 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 literal 57786 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%0AX&AoP5Xk|0dTiQx1fPsq2 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 Date: Wed, 27 Aug 2025 16:37:11 +0800 Subject: [PATCH 17/91] Optimize --- .../widget/CatalogueCheckBoxButton.java | 3 ++- .../screen/widget/CatalogueIconButton.java | 6 ++---- .../assets/forge/textures/gui/checkbox.png | Bin 2897 -> 2115 bytes .../assets/forge/textures/gui/icons.png | Bin 2455 -> 831 bytes 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java index e6f671db2..83b5f700c 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java @@ -5,6 +5,7 @@ import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.client.TEMPmodlist.ClientHelper; /** * Author: MrCrayfish @@ -38,7 +39,7 @@ public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partia GlStateManager.enableBlend(); GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - this.drawTexturedModalRect(this.x, this.y, this.hovered ? 14 : 0, this.selected ? 14 : 0, this.width, this.height); + ClientHelper.blit(this.x, this.y, this.hovered ? 14 : 0, this.selected() ? 14 : 0, 14, 14, 64, 64); this.mouseDragged(minecraft, mouseX, mouseY); } } diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java index 390676582..1a42633ce 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java @@ -7,6 +7,7 @@ import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.client.TEMPmodlist.ClientHelper; /** * Author: MrCrayfish @@ -37,15 +38,12 @@ public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partia FontRenderer fontrenderer = minecraft.fontRenderer; minecraft.getTextureManager().bindTexture(TEXTURE); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.enableBlend(); - GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); - GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); int contentWidth = 10 + fontrenderer.getStringWidth(this.label) + (!this.label.isEmpty() ? 4 : 0); int iconX = this.x + (this.width - contentWidth) / 2; int iconY = this.y + 5; float brightness = this.enabled ? 1.0F : 0.5F; GlStateManager.color(brightness, brightness, brightness, 1.0F); - this.drawTexturedModalRect(iconX, iconY, this.u, this.v, 10, 10); + ClientHelper.blit(iconX, iconY, this.u, this.v, 10, 10, 64, 64); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); int textColor = this.getFGColor() | MathHelper.ceil(255.0F) << 24; drawString(fontrenderer, this.label, iconX + 14, iconY + 1, textColor); diff --git a/src/main/resources/assets/forge/textures/gui/checkbox.png b/src/main/resources/assets/forge/textures/gui/checkbox.png index ab88fcb5fa7035ac6ab497c6bc0f26c889c22413..805910f455163521ff3771e4d7ed30784a2d7c69 100644 GIT binary patch delta 661 zcmca8c342MGr-TCmrII^fq{Y7)59eQNIQTq2OE%Fm05UiqM|qx>#vDcJ?f@VVqpUDj`sjTVUmzT9TNO zSd!?HSdu!qoKEPCa+_QtWVhA_LYHw@x7;uV@SoVx6|+TH5mxB zR$4MS91d}JUd#P{ z>i=!s7simRyD~U{HP*OA9yDft42$0>77gv$BJV{dEaw_wK3*w6BnB3VOyt=ZZFE>WF z`f&P;-6wx8U!DCpOX~giskKYI`5LmLj?I0z>8AN?*~_!H7uP@EU%$%Rh}p?^y^eQ& z)k~fq&ca(xrX6_vvBR%<)3VG%6Td&LyL_nfMbhO2kLgLmUuI7+mMOEFzxSk6+d(nc zMAf5#zpG1&c(h`RR5x6C@%n32d}8kX@27PhEP5mB`1k$QOoj`poDSD5=UjaMOKr!i zFXge173Z=s#7^=RU?};rrCzv7YRcoha}7(`kCa%Z9Jp1%>!0KS%sa8XRLGr-TCmrII^fq{Y7)59f*fq@aoWMtrA15z}eUL-P;= zBP%0gD+6O~0}Cqy10{uu+yX1#)RM%M#F9jp#FA87C7=md6;EbnE>ST^HAqY{H89XM zG&V@lO-(aN(M?QDHqcE?GB7Z*Ft@ZwHL%z`o7shFGC!-C7cr`p6pBk+%Sx=uGg9;L zSbmX3gT=^5&vf!W)~foQI$tUo7+CZ?T^vIyZoR$j+jq-Aq9L)YK!~I9LvW$YgA$#c z$+PqiotSLKnL2d^|E0x=tE$AN{W->$5_tV9(+~TZECLPBd_R8^uK%e#f1i(qk0rzJ zzx(t~Z;D%g{cG9o+i49AXfGMrIh*cg#~aoJIsBt`lDtW)%@2-p8 z5E{C7{iBy&a^c_hG4%8bGt4+CsBomoPKIx7jU&G-V;jhb7jh1_E_wUo9%KW##nZTlI)(tz7mK)bzzrdG}F^A!RPZ`6{v%7!H)PJ>5_P_-T zMuvD6qlXVxA84p&V7ScTQ0x~U61p|x+^4laY-K?1Dqa_FlK!ir{?(7HIi2mCtP6Bb zy9SD6Zv6Kl>c-DUYvarR`QPs|{%EjK*5SVI{;OG6PsRV_dmx|3bghiR;2^`C^Un`1 zXT1FSW5p5oy8W~M7k3<$f7~{Y?TvK$qr@n4#@nv5><;tKKD%scecbljYvc5$m)?4G z{O145g)fs24)a(YqhCi?7>OcCEdEiUo{cQFR``8Uk&has%eb98MTBX1D z-S^+}{>)|e?@rIJ2fArG`vG5v84tcQ?DA)R7X9N7hk*IVSx4{Iefz&_R)3#2qsL<( zn+hp`4<-r^5)~XkS%`%}fB`Q9uOzYo^#qNAsy?97z`(%7z`>xvKrMy>gBl~z_7m+R zMgvX~oLEnshd3HMkwcBxK*Q^3uw)SVMm?zB&!{u&u1M1RP;~|%@O1TaS?83{1ORAC BhaCU_ diff --git a/src/main/resources/assets/forge/textures/gui/icons.png b/src/main/resources/assets/forge/textures/gui/icons.png index 34e537c724d1adb9ec1e56d57c8ca829c2eac005..89b1a035ef9383885d7ce7aa6bef872405b90b8b 100644 GIT binary patch literal 831 zcmV-F1Hk-=P)Px%`AI}URCt{2)?Y|dQ5?YW@5NO{Wx8RJ(jT>=gp5!z67q2|dKp#^3n5ah*Is(6 zhv*?1^w2{@g1wY4)tC}3EGjUgE-WM)*1%RNTG`HYo0tZ9dzicKkC})V#P9R6ogcq* z@A;i`c6PMM$w?N<>-A>EoKB~ql%lJvOPgu4x7lmV_nbE^PAxMwJ=koF_uVDW`a`2S zF{a$y+y#$qv9z|!j;z71-E~T{+wED&jg5@}w6?Yapp-InT_+R@X+1qX0Qhqb8zoyd zQMRFA&KG_d8Rg@^7v8iuHP4mAyAQ5-3PA8%BIo(H-2hy;{Vr>wmYM~i-gN?i$oFBM zJ$}Ua`x}Pm{S7lI8Xn=~#cnNGc#_R#OKaEH*Bk!!Datk!5U6$mu=>F@0Q&bgP;ARf zBwjyf_9@(1%c_+xSzCUT1HgFSU8a8?;YG*2rLFW2EC8Q_Z_PkeWhLKLBrT~oJo3i} zI2;a?QbuWMDSG$#gK|oY|Ep6e*@cJ-C|?sRdAgLAT}^+_OGb4GmV{CGO(wa z3RvC=SZB3iEi5vFTS@E&fOtgB+0p0o88tOE=(;{rI0S=1jhr1uviPKUG=jx-o*@;> zdQN^()shvn7#1L{_*C(TVx85N6;CbRXnoTIECFhQtU&qd}xxAR1J8oD{Vlv7T zpVo|b{>xopo&lLF9gnEYv%mmIE&gjf&Wpi8bX_--N)FuwFjv27{VKxIDYix1_{8z7 z0gMg%$uFuhcR}j+o(o-==2pbda9Q(3VXvxi{s z>rN_C`i%|y>3!X~h*B0uTj)i|=kuBO1&6~i@Beejp_}twgeuN>wGR7zE;a3&H*Y!a z0YV5Ngb+dqA%qY@2qAn%jt(qZ864Ppp8iw(j^^klL(~&wIK^h3HMXe<%gN4$Yu#pdr>D}Vhz1n z@mfjZDS`+FgPLHjh7)R#uEj!-Izp#YBZOKE2P8VA4v4cx8SGGG1eTXrE`Z65^a`$C zvf{|s;UN5coHihiBnqXB2+=&D)sS*fLqHD`$MN-oZ^*cZA)pWXVL%cQRvX9iwVcF> zwcNi@$FJ`)fRgKU#%+G2faNS<10F6Y&b>nc+Gz<|aa1*fnl!7m1~*AkT0NyB=aFWL zB!{6+&ck}c|Agiu1f@6M1w~Ecp(XmhU=KriIl)h(fLT9X0|`D*gJbbDQZ~-V3CJ+= zF89uJht1{^I4|o%7h-uy5#}hi8MPLpQLWQxhun2KDMvt*=l}y9b}NpOqhVPO#pt~T z(xW%2EoRb$g4IK{1?GCy27RtUYchFF5Hh3sc8;l+TVPb*^FQ?$3M?8z+V{Xb@@UGv zLzS{3s%m&-bQZytBa)BBhT4Uq8F>t>I3w2|dhn5B_JK1nOdG6)i1a`DVi+cJUMWZm zu&@SY>%M(QAm<5rV29%%{vP7c-5tX{3P&457URnby~NiY44@q+pkG3!+>RH`S+PrJ zV^Tvy%Z8h$-;0szQv+>{tFLYAy)uFQeP>Je({mMd?wQsD%`L~StgHhK2dmW4rZ2MJ zTEI3oHV!PEc|$G~-L zkLpt2vnF+A#t9X+m)XaB&wUB*B{*$dhaH@}>HV|GD!Z&P>P;Abl*OQL z^M!G>GX{@YBl~~(GjA~ILeGy|3)2e111Fnq-#FA|jR+ZUx2b?JJ(Af|(cU>J@#U>Y zsuP#LuLCQON5kVg=K^02*dCkenz&}5t@l`j5NlQ+h*hoWh_260K9dEK6iSc|3gq9> zu@V$~`k3;`ex*MYN5?GWc Date: Wed, 27 Aug 2025 16:41:03 +0800 Subject: [PATCH 18/91] Change root folder name, remove the old mod list --- .../fml/client/FMLClientHandler.java | 2 +- .../minecraftforge/fml/client/GuiModList.java | 627 +----------------- .../ClientHelper.java | 2 +- .../screen/CatalogueContainer.java | 2 +- .../screen/CatalogueModListScreen.java | 12 +- .../screen/MinecraftContainer.java | 2 +- .../widget/CatalogueCheckBoxButton.java | 4 +- .../screen/widget/CatalogueIconButton.java | 4 +- .../screen/widget/CatalogueListExtended.java | 2 +- .../screen/widget/CatalogueTextField.java | 2 +- 10 files changed, 21 insertions(+), 638 deletions(-) rename src/main/java/net/minecraftforge/fml/client/{TEMPmodlist => modlist}/ClientHelper.java (97%) rename src/main/java/net/minecraftforge/fml/client/{TEMPmodlist => modlist}/screen/CatalogueContainer.java (95%) rename src/main/java/net/minecraftforge/fml/client/{TEMPmodlist => modlist}/screen/CatalogueModListScreen.java (99%) rename src/main/java/net/minecraftforge/fml/client/{TEMPmodlist => modlist}/screen/MinecraftContainer.java (94%) rename src/main/java/net/minecraftforge/fml/client/{TEMPmodlist => modlist}/screen/widget/CatalogueCheckBoxButton.java (94%) rename src/main/java/net/minecraftforge/fml/client/{TEMPmodlist => modlist}/screen/widget/CatalogueIconButton.java (94%) rename src/main/java/net/minecraftforge/fml/client/{TEMPmodlist => modlist}/screen/widget/CatalogueListExtended.java (99%) rename src/main/java/net/minecraftforge/fml/client/{TEMPmodlist => modlist}/screen/widget/CatalogueTextField.java (98%) diff --git a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java index a8949729b..c5a8e42e9 100644 --- a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java +++ b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java @@ -91,7 +91,7 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.common.util.CompoundDataFixer; -import net.minecraftforge.fml.client.TEMPmodlist.screen.CatalogueModListScreen; +import net.minecraftforge.fml.client.modlist.screen.CatalogueModListScreen; import net.minecraftforge.fml.client.registry.RenderingRegistry; import net.minecraftforge.fml.common.DummyModContainer; import net.minecraftforge.fml.common.DuplicateModsFoundException; diff --git a/src/main/java/net/minecraftforge/fml/client/GuiModList.java b/src/main/java/net/minecraftforge/fml/client/GuiModList.java index 0b925267e..21f05f4c7 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiModList.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiModList.java @@ -1,628 +1,11 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2020. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - package net.minecraftforge.fml.client; -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map.Entry; - -import javax.annotation.Nullable; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.GuiTextField; -import net.minecraft.client.gui.GuiUtilRenderComponents; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.texture.DynamicTexture; -import net.minecraft.client.renderer.texture.TextureManager; -import net.minecraft.client.renderer.texture.TextureUtil; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.client.resources.I18n; -import net.minecraft.client.resources.IResourcePack; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.TextComponentString; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.StringUtils; -import net.minecraftforge.common.ForgeHooks; -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.common.ForgeVersion.CheckResult; -import net.minecraftforge.common.ForgeVersion.Status; -import net.minecraftforge.fml.common.FMLLog; -import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.common.ModContainer; -import net.minecraftforge.fml.common.ModContainer.Disableable; -import net.minecraftforge.fml.common.ModMetadata; -import net.minecraftforge.fml.common.versioning.ComparableVersion; -import static net.minecraft.util.text.TextFormatting.*; - -import org.lwjgl.input.Mouse; - -import com.google.common.base.Strings; -import org.lwjgl.opengl.GL11; - -/** - * @author cpw - * - */ -public class GuiModList extends GuiScreen -{ - private enum SortType implements Comparator - { - NORMAL(24), - A_TO_Z(25){ @Override protected int compare(String name1, String name2){ return name1.compareTo(name2); }}, - Z_TO_A(26){ @Override protected int compare(String name1, String name2){ return name2.compareTo(name1); }}; - - private int buttonID; - - private SortType(int buttonID) - { - this.buttonID = buttonID; - } - - @Nullable - public static SortType getTypeForButton(GuiButton button) - { - for (SortType t : values()) - { - if (t.buttonID == button.id) - { - return t; - } - } - return null; - } - - protected int compare(String name1, String name2){ return 0; } - - @Override - public int compare(ModContainer o1, ModContainer o2) - { - String name1 = StringUtils.stripControlCodes(o1.getName()).toLowerCase(); - String name2 = StringUtils.stripControlCodes(o2.getName()).toLowerCase(); - return compare(name1, name2); - } - } - - private GuiScreen mainMenu; - private GuiSlotModList modList; - private GuiScrollingList modInfo; - private int selected = -1; - private ModContainer selectedMod; - private int listWidth; - private ArrayList mods; - private GuiButton configModButton; - private GuiButton disableModButton; - - private int buttonMargin = 1; - private int numButtons = SortType.values().length; - - private String lastFilterText = ""; - - private GuiTextField search; - private boolean sorted = false; - private SortType sortType = SortType.NORMAL; - - /** - * @param mainMenu - */ - public GuiModList(GuiScreen mainMenu) - { - this.mainMenu = mainMenu; - this.mods = new ArrayList(); - FMLClientHandler.instance().addSpecialModEntries(mods); - // Add child mods to their parent's list - for (ModContainer mod : Loader.instance().getModList()) - { - if (mod.getMetadata() != null && mod.getMetadata().parentMod == null && !Strings.isNullOrEmpty(mod.getMetadata().parent)) - { - String parentMod = mod.getMetadata().parent; - ModContainer parentContainer = Loader.instance().getIndexedModList().get(parentMod); - if (parentContainer != null) - { - mod.getMetadata().parentMod = parentContainer; - parentContainer.getMetadata().childMods.add(mod); - continue; - } - } - else if (mod.getMetadata() != null && mod.getMetadata().parentMod != null) - { - continue; - } - mods.add(mod); - } - } - - @Override - public void initGui() - { - int slotHeight = 25; - for (ModContainer mod : mods) - { - listWidth = Math.max(listWidth,getFontRenderer().getStringWidth(mod.getName()) + 10); - listWidth = Math.max(listWidth,getFontRenderer().getStringWidth(mod.getVersion()) + 5 + slotHeight); - } - listWidth = Math.min(listWidth, 150); - this.modList = new GuiSlotModList(this, mods, listWidth, slotHeight); - - this.buttonList.add(new GuiButton(6, ((modList.right + this.width) / 2) - 100, this.height - 38, I18n.format("gui.done"))); - configModButton = new GuiButton(20, 10, this.height - 49, this.listWidth, 20, "Config"); - disableModButton = new GuiButton(21, 10, this.height - 27, this.listWidth, 20, "Disable"); - this.buttonList.add(configModButton); - this.buttonList.add(disableModButton); - - search = new GuiTextField(0, getFontRenderer(), 12, modList.bottom + 17, modList.listWidth - 4, 14); - search.setFocused(true); - search.setCanLoseFocus(true); - - int width = (modList.listWidth / numButtons); - int x = 10, y = 10; - GuiButton normalSort = new GuiButton(SortType.NORMAL.buttonID, x, y, width - buttonMargin, 20, I18n.format("fml.menu.mods.normal")); - normalSort.enabled = false; - buttonList.add(normalSort); - x += width + buttonMargin; - buttonList.add(new GuiButton(SortType.A_TO_Z.buttonID, x, y, width - buttonMargin, 20, "A-Z")); - x += width + buttonMargin; - buttonList.add(new GuiButton(SortType.Z_TO_A.buttonID, x, y, width - buttonMargin, 20, "Z-A")); - - updateCache(); - } - - @Override - protected void mouseClicked(int x, int y, int button) throws IOException - { - super.mouseClicked(x, y, button); - search.mouseClicked(x, y, button); - if (button == 1 && x >= search.x && x < search.x + search.width && y >= search.y && y < search.y + search.height) { - search.setText(""); - } - } - - @Override - protected void keyTyped(char c, int keyCode) throws IOException - { - super.keyTyped(c, keyCode); - search.textboxKeyTyped(c, keyCode); - } - - @Override - public void updateScreen() - { - super.updateScreen(); - search.updateCursorCounter(); - - if (!search.getText().equals(lastFilterText)) - { - reloadMods(); - sorted = false; - } - - if (!sorted) - { - reloadMods(); - Collections.sort(mods, sortType); - selected = modList.selectedIndex = mods.indexOf(selectedMod); - sorted = true; - } - } - - private void reloadMods() - { - ArrayList mods = modList.getMods(); - mods.clear(); - for (ModContainer m : Loader.instance().getModList()) - { - // If it passes the filter, and is not a child mod - if (m.getName().toLowerCase().contains(search.getText().toLowerCase()) && (m.getMetadata() == null || m.getMetadata().parentMod == null)) - { - mods.add(m); - } - } - this.mods = mods; - lastFilterText = search.getText(); - } - - @Override - protected void actionPerformed(GuiButton button) throws IOException - { - if (button.enabled) - { - SortType type = SortType.getTypeForButton(button); - - if (type != null) - { - for (GuiButton b : buttonList) - { - if (SortType.getTypeForButton(b) != null) - { - b.enabled = true; - } - } - button.enabled = false; - sorted = false; - sortType = type; - this.mods = modList.getMods(); - } - else - { - switch (button.id) - { - case 6: - { - this.mc.displayGuiScreen(this.mainMenu); - return; - } - case 20: - { - try - { - IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod); - GuiScreen newScreen = guiFactory.createConfigGui(this); - this.mc.displayGuiScreen(newScreen); - } - catch (Exception e) - { - FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", selectedMod.getModId(), e); - } - return; - } - } - } - } - super.actionPerformed(button); - } - - public int drawLine(String line, int offset, int shifty) - { - this.fontRenderer.drawString(line, offset, shifty, 0xd7edea); - return shifty + 10; - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) - { - this.modList.drawScreen(mouseX, mouseY, partialTicks); - if (this.modInfo != null) - this.modInfo.drawScreen(mouseX, mouseY, partialTicks); - - int left = ((this.width - this.listWidth - 38) / 2) + this.listWidth + 30; - this.drawCenteredString(this.fontRenderer, "Mod List", left, 16, 0xFFFFFF); - super.drawScreen(mouseX, mouseY, partialTicks); - - String text = I18n.format("fml.menu.mods.search"); - int x = ((10 + modList.right) / 2) - (getFontRenderer().getStringWidth(text) / 2); - getFontRenderer().drawString(text, x, modList.bottom + 5, 0xFFFFFF); - search.drawTextBox(); - } - - @Override - public void handleMouseInput() throws IOException - { - int mouseX = Mouse.getEventX() * this.width / this.mc.displayWidth; - int mouseY = this.height - Mouse.getEventY() * this.height / this.mc.displayHeight - 1; - - super.handleMouseInput(); - if (this.modInfo != null) - this.modInfo.handleMouseInput(mouseX, mouseY); - this.modList.handleMouseInput(mouseX, mouseY); - } - - Minecraft getMinecraftInstance() - { - return mc; - } - - FontRenderer getFontRenderer() - { - return fontRenderer; - } - - public void selectModIndex(int index) - { - if (index == this.selected) - return; - this.selected = index; - this.selectedMod = (index >= 0 && index <= mods.size()) ? mods.get(selected) : null; - - updateCache(); - } - - public boolean modIndexSelected(int index) - { - return index == selected; - } - - private void updateCache() - { - configModButton.visible = false; - disableModButton.visible = false; - modInfo = null; - - if (selectedMod == null) - return; - - ResourceLocation logoPath = null; - Dimension logoDims = new Dimension(0, 0); - List lines = new ArrayList(); - ModMetadata metadata = selectedMod.getMetadata(); - if (metadata != null) { - String logoFile = metadata.logoFile; - if (!logoFile.isEmpty()) { - TextureManager tm = mc.getTextureManager(); - IResourcePack pack = FMLClientHandler.instance().getResourcePackFor(selectedMod.getModId()); - try { - BufferedImage logo = null; - if (pack != null) { - logo = pack.getPackImage(); - } else { - InputStream logoResource = getClass().getResourceAsStream(logoFile); - if (logoResource != null) - logo = TextureUtil.readBufferedImage(logoResource); - } - if (logo != null) { - logoPath = tm.getDynamicTextureLocation("modlogo", new DynamicTexture(logo)); - logoDims = new Dimension(logo.getWidth(), logo.getHeight()); - } - } catch (IOException ignored) { - } - } - } - - if (metadata != null && !metadata.autogenerated) - { - disableModButton.visible = true; - disableModButton.enabled = true; - disableModButton.packedFGColour = 0; - Disableable disableable = selectedMod.canBeDisabled(); - if (disableable == Disableable.RESTART) - { - disableModButton.packedFGColour = 0xFF3377; - } - else if (disableable != Disableable.YES) - { - disableModButton.enabled = false; - } - - IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod); - configModButton.visible = true; - configModButton.enabled = false; - if (guiFactory != null) - { - configModButton.enabled = guiFactory.hasConfigGui(); - } - lines.add(metadata.name); - if (selectedMod.getDisplayVersion().isEmpty()) - lines.add(String.format("Version: %s", selectedMod.getVersion())); - else if (selectedMod.getVersion().equals(selectedMod.getDisplayVersion())) - lines.add(String.format("Version: %s", selectedMod.getDisplayVersion())); - else - lines.add(String.format("Version: %s (%s)", selectedMod.getDisplayVersion(), selectedMod.getVersion())); - lines.add(String.format("Mod ID: '%s' Mod State: %s", selectedMod.getModId(), Loader.instance().getModState(selectedMod))); - - if (!metadata.credits.isEmpty()) - { - lines.add("Credits: " + metadata.credits); - } - - if (!metadata.getAuthorList().isEmpty()) - lines.add("Authors: " + metadata.getAuthorList()); - if (!metadata.url.isEmpty()) - lines.add("URL: " + metadata.url); - - if (!metadata.childMods.isEmpty()) - lines.add("Child mods: " + metadata.getChildModList()); - } - else - { - lines.add(WHITE + selectedMod.getName()); - lines.add(WHITE + "Version: " + selectedMod.getVersion()); - lines.add(WHITE + "Mod State: " + Loader.instance().getModState(selectedMod)); - } - - CheckResult vercheck = ForgeVersion.getCleanResult(selectedMod); - if (vercheck != null && (vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED || vercheck.status == Status.BETA || vercheck.status == Status.AHEAD)) - { - lines.add(null); - if (vercheck.status == Status.BETA) - lines.add(GOLD + "This version is a beta version"); - else if (vercheck.status == Status.AHEAD) - lines.add(LIGHT_PURPLE + "This version is ahead of the latest version found (" + LIGHT_PURPLE + vercheck.latestFound + ")"); - else if (vercheck.status == Status.BETA_OUTDATED) - lines.add(GOLD + "Beta update (" + GOLD + vercheck.latestFound + ") available: " + (vercheck.homepage == null ? "" : vercheck.homepage)); - else - lines.add(GREEN + "Update (" + GREEN + vercheck.latestFound + ") available: " + (vercheck.homepage == null ? "" : vercheck.homepage)); - - if (!vercheck.changes.isEmpty()) - { - lines.add(null); - lines.add("Changes:"); - for (Entry entry : vercheck.changes.entrySet()) - { - lines.add(" " + entry.getKey() + ":"); - lines.add(entry.getValue()); - lines.add(null); - } - } - } - - if (metadata != null && !metadata.autogenerated) - { - lines.add(null); - lines.add(metadata.description); - } - else - { - lines.add(null); - lines.add(RED + "No mod information found"); - lines.add(RED + "Ask the mod author to provide a mcmod.info file"); - } - - modInfo = new Info(this.width - this.listWidth - 30, lines, logoPath, logoDims); - } - - private class Info extends GuiScrollingList - { - @Nullable - private ResourceLocation logoPath; - private Dimension logoDims; - private List lines = null; - - public Info(int width, List lines, @Nullable ResourceLocation logoPath, Dimension logoDims) - { - super(GuiModList.this.getMinecraftInstance(), - width, - GuiModList.this.height, - 32, GuiModList.this.height - 88 + 4, - GuiModList.this.listWidth + 20, 60, - GuiModList.this.width, - GuiModList.this.height); - this.lines = resizeContent(lines); - this.logoPath = logoPath; - this.logoDims = logoDims; - - this.setHeaderInfo(true, getHeaderHeight()); - } - - @Override protected int getSize() { return 0; } - @Override protected void elementClicked(int index, boolean doubleClick) { } - @Override protected boolean isSelected(int index) { return false; } - @Override protected void drawBackground() {} - @Override protected void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess) { } - - private List resizeContent(List lines) - { - List ret = new ArrayList(); - for (String line : lines) - { - if (line == null) - { - ret.add(null); - continue; - } - - ITextComponent chat = ForgeHooks.newChatWithLinks(line, false); - int maxTextLength = this.listWidth - 8; - if (maxTextLength >= 0) - { - ret.addAll(GuiUtilRenderComponents.splitText(chat, maxTextLength, GuiModList.this.fontRenderer, false, true)); - } - } - return ret; - } - - private int getHeaderHeight() - { - int height = 0; - if (logoPath != null) - { - double scaleX = logoDims.width / 200.0; - double scaleY = logoDims.height / 65.0; - double scale = 1.0; - if (scaleX > 1 || scaleY > 1) - { - scale = 1.0 / Math.max(scaleX, scaleY); - } - logoDims.width *= scale; - logoDims.height *= scale; - - height += logoDims.height; - height += 10; - } - height += (lines.size() * 10); - if (height < this.bottom - this.top - 8) height = this.bottom - this.top - 8; - return height; - } - - - @Override - protected void drawHeader(int entryRight, int relativeY, Tessellator tess) - { - int top = relativeY; - - if (logoPath != null) - { - GlStateManager.enableBlend(); - GuiModList.this.mc.renderEngine.bindTexture(logoPath); - BufferBuilder wr = tess.getBuffer(); - int offset = (this.left + this.listWidth/2) - (logoDims.width / 2); - wr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - wr.pos(offset, top + logoDims.height, zLevel).tex(0, 1).endVertex(); - wr.pos(offset + logoDims.width, top + logoDims.height, zLevel).tex(1, 1).endVertex(); - wr.pos(offset + logoDims.width, top, zLevel).tex(1, 0).endVertex(); - wr.pos(offset, top, zLevel).tex(0, 0).endVertex(); - tess.draw(); - GlStateManager.disableBlend(); - top += logoDims.height + 10; - } - - for (ITextComponent line : lines) - { - if (line != null) - { - GlStateManager.enableBlend(); - GuiModList.this.fontRenderer.drawStringWithShadow(line.getFormattedText(), this.left + 4, top, 0xFFFFFF); - GlStateManager.disableAlpha(); - GlStateManager.disableBlend(); - } - top += 10; - } - } - - @Override - protected void clickHeader(int x, int y) - { - int offset = y; - if (logoPath != null) { - offset -= logoDims.height + 10; - } - if (offset <= 0) - return; - - int lineIdx = offset / 10; - if (lineIdx >= lines.size()) - return; +import net.minecraftforge.fml.client.modlist.screen.CatalogueModListScreen; - ITextComponent line = lines.get(lineIdx); - if (line != null) - { - int k = -4; - for (ITextComponent part : line) { - if (!(part instanceof TextComponentString)) - continue; - k += GuiModList.this.fontRenderer.getStringWidth(((TextComponentString)part).getText()); - if (k >= x) - { - GuiModList.this.handleComponentClick(part); - break; - } - } - } - } +@Deprecated +public class GuiModList extends CatalogueModListScreen { + public GuiModList(GuiScreen mainMenu) { + super(mainMenu); } } diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ClientHelper.java b/src/main/java/net/minecraftforge/fml/client/modlist/ClientHelper.java similarity index 97% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ClientHelper.java rename to src/main/java/net/minecraftforge/fml/client/modlist/ClientHelper.java index b1e36b6aa..1caed7d0a 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ClientHelper.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/ClientHelper.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.TEMPmodlist; +package net.minecraftforge.fml.client.modlist; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueContainer.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java similarity index 95% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueContainer.java rename to src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java index 8544f6298..f2e228403 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueContainer.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.TEMPmodlist.screen; +package net.minecraftforge.fml.client.modlist.screen; import net.minecraftforge.fml.common.DummyModContainer; import net.minecraftforge.fml.common.ModMetadata; diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java similarity index 99% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java rename to src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java index f387b3f0f..a38d4d4be 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.TEMPmodlist.screen; +package net.minecraftforge.fml.client.modlist.screen; import com.google.common.collect.Lists; import net.minecraft.client.Minecraft; @@ -27,11 +27,11 @@ import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.client.IModGuiFactory; -import net.minecraftforge.fml.client.TEMPmodlist.ClientHelper; -import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueCheckBoxButton; -import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueIconButton; -import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueListExtended; -import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueTextField; +import net.minecraftforge.fml.client.modlist.ClientHelper; +import net.minecraftforge.fml.client.modlist.screen.widget.CatalogueCheckBoxButton; +import net.minecraftforge.fml.client.modlist.screen.widget.CatalogueIconButton; +import net.minecraftforge.fml.client.modlist.screen.widget.CatalogueListExtended; +import net.minecraftforge.fml.client.modlist.screen.widget.CatalogueTextField; import net.minecraftforge.fml.common.FMLLog; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.ModContainer; diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/MinecraftContainer.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/MinecraftContainer.java similarity index 94% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/MinecraftContainer.java rename to src/main/java/net/minecraftforge/fml/client/modlist/screen/MinecraftContainer.java index 88d5f5342..ba02f00b5 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/MinecraftContainer.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/MinecraftContainer.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.TEMPmodlist.screen; +package net.minecraftforge.fml.client.modlist.screen; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.common.DummyModContainer; diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueCheckBoxButton.java similarity index 94% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java rename to src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueCheckBoxButton.java index 83b5f700c..84a9ad702 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueCheckBoxButton.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueCheckBoxButton.java @@ -1,11 +1,11 @@ -package net.minecraftforge.fml.client.TEMPmodlist.screen.widget; +package net.minecraftforge.fml.client.modlist.screen.widget; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.client.TEMPmodlist.ClientHelper; +import net.minecraftforge.fml.client.modlist.ClientHelper; /** * Author: MrCrayfish diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueIconButton.java similarity index 94% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java rename to src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueIconButton.java index 1a42633ce..374deab45 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueIconButton.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueIconButton.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.TEMPmodlist.screen.widget; +package net.minecraftforge.fml.client.modlist.screen.widget; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; @@ -7,7 +7,7 @@ import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.client.TEMPmodlist.ClientHelper; +import net.minecraftforge.fml.client.modlist.ClientHelper; /** * Author: MrCrayfish diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueListExtended.java similarity index 99% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java rename to src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueListExtended.java index d203ec769..93b4e4943 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueListExtended.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.TEMPmodlist.screen.widget; +package net.minecraftforge.fml.client.modlist.screen.widget; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiListExtended; diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueTextField.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueTextField.java similarity index 98% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueTextField.java rename to src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueTextField.java index 9eafff80d..f27330250 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueTextField.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueTextField.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.TEMPmodlist.screen.widget; +package net.minecraftforge.fml.client.modlist.screen.widget; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; From b8aa36252004d36d735e714550a2ecdf97582c61 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:42:16 +0800 Subject: [PATCH 19/91] Delete GuiSlotModList.java --- .../fml/client/GuiSlotModList.java | 132 ------------------ 1 file changed, 132 deletions(-) delete mode 100644 src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java diff --git a/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java b/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java deleted file mode 100644 index af68601de..000000000 --- a/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2020. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.fml.client; - -import java.util.ArrayList; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.Gui; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.StringUtils; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.TextComponentString; -import net.minecraft.util.text.TextFormatting; -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.common.ForgeVersion.CheckResult; -import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.common.LoaderState.ModState; -import net.minecraftforge.fml.common.ModContainer; - -import static net.minecraft.util.text.TextFormatting.*; - -/** - * @author cpw - * - */ -public class GuiSlotModList extends GuiScrollingList -{ - - private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); - - private GuiModList parent; - private ArrayList mods; - - public GuiSlotModList(GuiModList parent, ArrayList mods, int listWidth, int slotHeight) - { - super(parent.getMinecraftInstance(), listWidth, parent.height, 32, parent.height - 88 + 4, 10, slotHeight, parent.width, parent.height); - this.parent = parent; - this.mods = mods; - } - - @Override - protected int getSize() - { - return mods.size(); - } - - @Override - protected void elementClicked(int index, boolean doubleClick) - { - this.parent.selectModIndex(index); - } - - @Override - protected boolean isSelected(int index) - { - return this.parent.modIndexSelected(index); - } - - @Override - protected void drawBackground() - { - this.parent.drawDefaultBackground(); - } - - @Override - protected int getContentHeight() - { - return (this.getSize()) * 25 + 1; - } - - ArrayList getMods() - { - return mods; - } - - @Override - protected void drawSlot(int idx, int right, int top, int height, Tessellator tess) - { - ModContainer mc = mods.get(idx); - String name = StringUtils.stripControlCodes(mc.getName()); - String version = StringUtils.stripControlCodes(mc.getDisplayVersion().isEmpty() ? mc.getVersion() : mc.getDisplayVersion()); - FontRenderer font = this.parent.getFontRenderer(); - CheckResult vercheck = ForgeVersion.getCleanResult(mc); - - if (Loader.instance().getModState(mc) == ModState.DISABLED) - { - drawString(name, 10, top + 2, RED); - drawString(version, 5 + height, top + 12, RED); - } - else - { - drawString(name, 10, top + 2, WHITE); - drawString(version, 5 + height, top + 12, WHITE); - - if (vercheck != null && vercheck.status.shouldDraw()) - { - Minecraft.getMinecraft().getTextureManager().bindTexture(VERSION_CHECK_ICONS); - GlStateManager.color(1, 1, 1, 1); - GlStateManager.pushMatrix(); - Gui.drawModalRectWithCustomSizedTexture(right - (height / 2 + 4), top + (height / 2 - 4), vercheck.status.getSheetOffset() * 8, (vercheck.status.isAnimated() && ((System.currentTimeMillis() / 800 & 1)) == 1) ? 8 : 0, 8, 8, 64, 16); - GlStateManager.popMatrix(); - } - } - } - - protected void drawString(String text, int width, int y, TextFormatting color) - { - FontRenderer font = parent.getFontRenderer(); - ITextComponent textComponent = new TextComponentString(color + font.trimStringToWidth(text, listWidth - width)); - font.drawString(textComponent.getFormattedText(), left + 3, y, 0); - } -} From 0ae99b9d103f0e26f48ee0ea8b479e5770233ae6 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Wed, 27 Aug 2025 18:15:52 +0800 Subject: [PATCH 20/91] =?UTF-8?q?=F0=9F=93=A6=20Cleaned=20up=20banner=20an?= =?UTF-8?q?d=20icon=20drawing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/MrCrayfish/Catalogue/commit/f7321b98a40193c59878b2c9cb55afd551da7857 --- .../screen/CatalogueModListScreen.java | 202 +++++++++--------- 1 file changed, 99 insertions(+), 103 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java index a38d4d4be..a4e57e1be 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java @@ -11,7 +11,6 @@ import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.TextureManager; -import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.client.renderer.texture.TextureUtil; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.resources.I18n; @@ -37,12 +36,10 @@ import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.ModMetadata; import net.minecraftforge.fml.common.registry.ForgeRegistries; -import org.apache.commons.lang3.tuple.Pair; import org.lwjgl.opengl.GL11; import javax.annotation.Nullable; import java.awt.Desktop; -import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; @@ -54,8 +51,9 @@ public class CatalogueModListScreen extends GuiScreen { private static final ResourceLocation MISSING_BACKGROUND = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/missing_background.png"); private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); private static final ResourceLocation MINECRAFT_LOGO = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/minecraft.png"); - private static final Map> BANNER_CACHE = new HashMap<>(); - private static final Map> IMAGE_ICON_CACHE = new HashMap<>(); + private static final ImageInfo MISSING_BANNER_INFO = new ImageInfo(MISSING_BANNER, new Dimension(120, 120)); + private static final Map BANNER_CACHE = new HashMap<>(); + private static final Map IMAGE_ICON_CACHE = new HashMap<>(); private static final Map ITEM_ICON_CACHE = new HashMap<>(); private static final Map CACHED_MODS = new HashMap<>(); private static ResourceLocation cachedBackground; @@ -85,6 +83,7 @@ public CatalogueModListScreen(GuiScreen parent) { Loader.instance().getActiveModList().forEach(data -> CACHED_MODS.put(data.getModId(), data)); CACHED_MODS.put("minecraft", new MinecraftContainer()); // Override minecraft CACHED_MODS.put("catalogue", new CatalogueContainer()); + BANNER_CACHE.put("minecraft", new ImageInfo(MINECRAFT_LOGO, new Dimension(1024, 256))); loaded = true; } } @@ -186,11 +185,10 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { Optional optional = Optional.ofNullable(CACHED_MODS.get("catalogue")); optional.ifPresent(this::loadAndCacheLogo); - Pair pair = BANNER_CACHE.get("catalogue"); - if (pair != null && pair.getLeft() != null) { - ResourceLocation textureId = pair.getLeft(); - Dimension size = pair.getRight(); - mc.getTextureManager().bindTexture(textureId); + ImageInfo imageInfo = BANNER_CACHE.get("catalogue"); + if (imageInfo != null) { + Dimension size = imageInfo.size(); + this.mc.getTextureManager().bindTexture(imageInfo.resource()); ClientHelper.blit(10, 9, 10, 10, 0.0F, 0.0F, size.width, size.height, size.width, size.height); } @@ -324,10 +322,12 @@ protected void overlayBackground(int startY, int endY, int startAlpha, int endAl private class ModListEntry implements CatalogueListExtended.IGuiListEntry { private final ModContainer data; private final ModList list; + private ItemStack icon; public ModListEntry(ModContainer data, ModList list) { this.data = data; this.list = list; + this.icon = this.getItemIcon(); } @Override @@ -336,35 +336,8 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, drawString(fontRenderer, this.getFormattedModName(), left + 24, top + 2, 0xFFFFFF); drawString(fontRenderer, TextFormatting.GRAY + this.data.getDisplayVersion(), left + 24, top + 12, 0xFFFFFF); - CatalogueModListScreen.this.loadAndCacheIcon(this.data); - - // Draw icon - if (IMAGE_ICON_CACHE.containsKey(this.data.getModId()) && IMAGE_ICON_CACHE.get(this.data.getModId()).getLeft() != null) { - ResourceLocation logoResource = TextureMap.LOCATION_MISSING_TEXTURE; - Dimension size = new Dimension(16, 16); - - Pair logoInfo = IMAGE_ICON_CACHE.get(this.data.getModId()); - if (logoInfo != null && logoInfo.getLeft() != null) { - logoResource = logoInfo.getLeft(); - size = logoInfo.getRight(); - } - - mc.getTextureManager().bindTexture(logoResource); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.enableBlend(); - ClientHelper.blit(left + 4, top + 2, 16, 16, 0.0F, 0.0F, size.width, size.height, size.width, size.height); - GlStateManager.disableBlend(); - } else { - try { - GlStateManager.enableDepth(); - RenderHelper.enableGUIStandardItemLighting(); - CatalogueModListScreen.this.mc.getRenderItem().renderItemIntoGUI(this.getItemIcon(), left + 4, top + 2); - GlStateManager.disableDepth(); - RenderHelper.disableStandardItemLighting(); - } catch (Exception e) { - ITEM_ICON_CACHE.put(this.data.getModId(), new ItemStack(Blocks.GRASS)); - } - } + // Draw image icon or fallback to item icon + this.drawIcon(top, left); // Draws an icon if there is an update for the mod ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.data); @@ -376,6 +349,33 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, } } + private void drawIcon(int top, int left) { + CatalogueModListScreen.this.loadAndCacheIcon(this.data); + + ImageInfo iconInfo = IMAGE_ICON_CACHE.get(this.data.getModId()); + if (iconInfo != null) { + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + Dimension size = iconInfo.size(); + mc.getTextureManager().bindTexture(iconInfo.resource()); + ClientHelper.blit(left + 4, top + 3, 16, 16, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + GlStateManager.disableBlend(); + return; + } + try { + GlStateManager.enableDepth(); + RenderHelper.enableGUIStandardItemLighting(); + CatalogueModListScreen.this.mc.getRenderItem().renderItemIntoGUI(this.icon, left + 4, top + 2); + GlStateManager.disableDepth(); + RenderHelper.disableStandardItemLighting(); + } catch (Exception e) { + // Attempt to catch exceptions when rendering item. Sometime level instance isn't checked for null + FMLLog.log.debug("Failed to draw icon for mod '{}'", this.data.getModId()); + ITEM_ICON_CACHE.put(this.data.getModId(), new ItemStack(Blocks.GRASS)); + this.icon = new ItemStack(Blocks.GRASS); + } + } + @Override public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { this.list.selectMod(slotIndex); @@ -649,7 +649,7 @@ public void updateScreen() { private void drawModList(int mouseX, int mouseY, float partialTicks) { GlStateManager.enableBlend(); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); + this.mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); ClientHelper.blit(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); GlStateManager.disableBlend(); @@ -711,7 +711,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); if (shouldDraw(update) && update.url != null) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); + this.mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); int vOffset = update.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; ClientHelper.blit(contentLeft + versionWidth + 5, 92, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { @@ -809,7 +809,7 @@ private void loadAndCacheLogo(ModContainer data) { if (BANNER_CACHE.containsKey(data.getModId())) return; // Fills an empty logo as logo may not be present - BANNER_CACHE.put(data.getModId(), Pair.of(null, new Dimension(0, 0))); + BANNER_CACHE.put(data.getModId(), null); // Attempts to load the real logo ModMetadata metadata = data.getMetadata(); @@ -830,8 +830,10 @@ private void loadAndCacheLogo(ModContainer data) { if (is != null) image = TextureUtil.readBufferedImage(is); } if (image == null) return; - TextureManager textureManager = this.mc.getTextureManager(); - BANNER_CACHE.put(data.getModId(), Pair.of(textureManager.getDynamicTextureLocation("modlogo", this.createLogoTexture(image, metadata.logoBlur)), new Dimension(image.getWidth(), image.getHeight()))); + TextureManager manager = this.mc.getTextureManager(); + ResourceLocation resource = manager.getDynamicTextureLocation("modlogo", this.createLogoTexture(image, metadata.logoBlur)); + Dimension size = new Dimension(image.getWidth(), image.getHeight()); + BANNER_CACHE.put(data.getModId(), new ImageInfo(resource, size)); } catch (IOException ignored) { } } @@ -840,7 +842,7 @@ private void loadAndCacheIcon(ModContainer data) { if (IMAGE_ICON_CACHE.containsKey(data.getModId())) return; // Fills an empty icon as icon may not be present - IMAGE_ICON_CACHE.put(data.getModId(), Pair.of(null, new Dimension(0, 0))); + IMAGE_ICON_CACHE.put(data.getModId(), null); ModMetadata metadata = data.getMetadata(); if (metadata == null) return; @@ -855,8 +857,10 @@ private void loadAndCacheIcon(ModContainer data) { try (InputStream is = getClass().getResourceAsStream(iconFile)) { if (is != null) image = TextureUtil.readBufferedImage(is); if (image != null) { - TextureManager textureManager = this.mc.getTextureManager(); - IMAGE_ICON_CACHE.put(data.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(image, metadata.iconBlur)), new Dimension(image.getWidth(), image.getHeight()))); + TextureManager manager = this.mc.getTextureManager(); + ResourceLocation resource = manager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(image, metadata.logoBlur)); + Dimension size = new Dimension(image.getWidth(), image.getHeight()); + IMAGE_ICON_CACHE.put(data.getModId(), new ImageInfo(resource, size)); return; } } catch (IOException ignored) { @@ -878,26 +882,23 @@ private void loadAndCacheIcon(ModContainer data) { InputStream is = getClass().getResourceAsStream(logoFile); if (is != null) image = TextureUtil.readBufferedImage(is); } - if (image != null && image.getWidth() == image.getHeight()) { - TextureManager textureManager = this.mc.getTextureManager(); - String modId = data.getModId(); - - /* The first selected mod will have its logo cached before the icon, so we - * can just use the logo instead of loading the image again. */ - if (BANNER_CACHE.containsKey(modId)) { - if (BANNER_CACHE.get(modId).getLeft() != null) { - IMAGE_ICON_CACHE.put(modId, BANNER_CACHE.get(modId)); - return; - } - } + if (image == null || image.getWidth() != image.getHeight()) return; - /* Since the icon will be same as the logo, we can cache into both icon and logo cache */ - DynamicTexture texture = this.createLogoTexture(image, metadata.logoBlur); - Dimension size = new Dimension(image.getWidth(), image.getHeight()); - ResourceLocation textureId = textureManager.getDynamicTextureLocation("catalogueicon", texture); - IMAGE_ICON_CACHE.put(modId, Pair.of(textureId, size)); - BANNER_CACHE.put(modId, Pair.of(textureId, size)); + /* The first selected mod will have its logo cached before the icon, so we + * can just use the logo instead of loading the image again. */ + String modId = data.getModId(); + if (BANNER_CACHE.containsKey(modId)) { + IMAGE_ICON_CACHE.put(modId, BANNER_CACHE.get(modId)); + return; } + + /* Since the icon will be same as the logo, we can cache into both icon and logo cache */ + TextureManager manager = this.mc.getTextureManager(); + DynamicTexture texture = this.createLogoTexture(image, metadata.logoBlur); + Dimension size = new Dimension(image.getWidth(), image.getHeight()); + ResourceLocation resource = manager.getDynamicTextureLocation("catalogueicon", texture); + IMAGE_ICON_CACHE.put(modId, new ImageInfo(resource, size)); + BANNER_CACHE.put(modId, new ImageInfo(resource, size)); } catch (IOException ignored) { } } @@ -906,8 +907,7 @@ private void loadAndCacheIcon(ModContainer data) { private void loadAndCacheBackground(ModContainer data) { // Deletes the last cached background since they are large images if (cachedBackground != null) { - TextureManager textureManager = this.mc.getTextureManager(); - textureManager.deleteTexture(cachedBackground); + this.mc.getTextureManager().deleteTexture(cachedBackground); } cachedBackground = null; @@ -944,7 +944,7 @@ private void drawBackground(int contentWidth, int x, int y) { if(this.selectedModData == null) return; ResourceLocation texture = cachedBackground != null ? cachedBackground : MISSING_BACKGROUND; - this.mc.renderEngine.bindTexture(texture); + this.mc.getTextureManager().bindTexture(texture); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); GlStateManager.disableAlpha(); @@ -968,40 +968,10 @@ private void drawBackground(int contentWidth, int x, int y) { @SuppressWarnings("SameParameterValue") private void drawBanner(int contentWidth, int x, int y, int maxWidth, int maxHeight) { if (this.selectedModData != null) { - ResourceLocation logoResource = MISSING_BANNER; - Dimension size = new Dimension(120, 120); - - if (BANNER_CACHE.containsKey(this.selectedModData.getModId())) { - Pair logoInfo = BANNER_CACHE.get(this.selectedModData.getModId()); - if (logoInfo.getLeft() != null) { - logoResource = logoInfo.getLeft(); - size = logoInfo.getRight(); - } - } - - int scale = 1; - if (logoResource == MISSING_BANNER) { - Pair logoInfo = IMAGE_ICON_CACHE.get(this.selectedModData.getModId()); - if (logoInfo.getLeft() != null) { - logoResource = logoInfo.getLeft(); - size = logoInfo.getRight(); - scale = 10; // Hack to make icon fill max banner height - } - } - - boolean offset = false; - if (this.selectedModData.getModId().equals("minecraft")) { - logoResource = MINECRAFT_LOGO; - size = new Dimension(1024, 256); - offset = true; - } - - mc.getTextureManager().bindTexture(logoResource); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.enableBlend(); - - int width = size.width * scale; - int height = size.height * scale; + ImageInfo bannerInfo = this.getBanner(this.selectedModData.getModId()); + Dimension size = bannerInfo.size(); + int width = size.width; + int height = size.height; if (size.width > maxWidth) { width = maxWidth; height = (width * size.height) / size.width; @@ -1014,16 +984,38 @@ private void drawBanner(int contentWidth, int x, int y, int maxWidth, int maxHei x += (contentWidth - width) / 2; y += (maxHeight - height) / 2; - if (offset) { // Fix for minecraft logo + // Fix for minecraft logo + if (bannerInfo.resource() == MINECRAFT_LOGO) { y += 8; } + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + this.mc.getTextureManager().bindTexture(bannerInfo.resource()); ClientHelper.blit(x, y, width, height, 0.0F, 0.0F, size.width, size.height, size.width, size.height); GlStateManager.disableBlend(); } } + private ImageInfo getBanner(String modId) { + // Try getting the banner for the mod + ImageInfo bannerInfo = BANNER_CACHE.get(modId); + if (bannerInfo != null) return bannerInfo; + + // Try using the icon image for the banner + ImageInfo iconInfo = IMAGE_ICON_CACHE.get(modId); + if (iconInfo != null) { + // Hack to make icon fill max banner height + Dimension size = iconInfo.size(); + Dimension newSize = new Dimension(size.width * 10, size.height * 10); + return new ImageInfo(iconInfo.resource(), newSize); + } + + // Fallback and just use missing banner + return MISSING_BANNER_INFO; + } + private void setActiveTooltip(String content) { this.activeTooltip = this.fontRenderer.listFormattedStringToWidth(content, 200); this.tooltipYOffset = 0; @@ -1121,6 +1113,10 @@ private void openLink(String url) { this.handleComponentClick(new TextComponentString("").setStyle(style)); } + private record Dimension(int width, int height) {} + + private record ImageInfo(ResourceLocation resource, Dimension size) {} + private boolean shouldDraw(ForgeVersion.CheckResult update) { return update != null && update.status.shouldDraw(); } From e28f8d8666ace24bba3bfea5a3a5a20809fe1ec1 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Wed, 27 Aug 2025 18:31:28 +0800 Subject: [PATCH 21/91] =?UTF-8?q?=E2=9C=A8=20Added=20Ctrl=20+=20F=20to=20f?= =?UTF-8?q?ocus=20search=20textfield?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/MrCrayfish/Catalogue/commit/65b4d23a396113c4e9251f4337f4066e82da6bdc --- .../modlist/screen/CatalogueModListScreen.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java index a4e57e1be..400c1aa0a 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java @@ -36,6 +36,8 @@ import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.ModMetadata; import net.minecraftforge.fml.common.registry.ForgeRegistries; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.GL11; import javax.annotation.Nullable; @@ -221,6 +223,18 @@ protected void keyTyped(char typedChar, int key) throws IOException { super.keyTyped(typedChar, key); } + @Override + public void handleKeyboardInput() throws IOException { + if (Keyboard.getEventKey() == Keyboard.KEY_F && (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL))) { + if(!this.searchTextField.isFocused()) { + this.searchTextField.setFocused(true); + } + return; + } + + super.handleKeyboardInput(); + } + private class ModList extends CatalogueListExtended { private List entries = Lists.newArrayList(); private int selectedIndex = -1; From b989b98d031f0dbe8ab702e6e85cc47ef9e400b5 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:29:53 +0800 Subject: [PATCH 22/91] Slight tweaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ Added tooltip to show numbers of mods/libraries 📦 Show "no mods" label when mod list is empty --- .../modlist/screen/CatalogueModListScreen.java | 16 ++++++++++++++-- src/main/resources/assets/forge/lang/en_us.lang | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java index 400c1aa0a..b962add01 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java @@ -36,7 +36,6 @@ import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.ModMetadata; import net.minecraftforge.fml.common.registry.ForgeRegistries; -import org.lwjgl.glfw.GLFW; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.GL11; @@ -248,6 +247,13 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { ClientHelper.scissor(this.getListLeft(), this.top, this.width, this.bottom - this.top); super.drawScreen(mouseX, mouseY, partialTicks); GL11.glDisable(GL11.GL_SCISSOR_TEST); + + if (this.entries.isEmpty()) { + String text = I18n.format("fml.menu.mods.nomods"); + int left = this.left + this.width / 2; + int top = this.top + (this.bottom - this.top - CatalogueModListScreen.this.fontRenderer.FONT_HEIGHT) / 2; + drawCenteredString(CatalogueModListScreen.this.fontRenderer, text, left, top, 0xFFFFFFFF); + } } @Override @@ -668,9 +674,15 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { GlStateManager.disableBlend(); this.modList.drawScreen(mouseX, mouseY, partialTicks); - drawString(this.fontRenderer, TextFormatting.BOLD + I18n.format("fml.menu.mods.title"), 70, 10, 0xFFFFFF); this.searchTextField.drawTextBox(); + String modsLabel = TextFormatting.BOLD + I18n.format("fml.menu.mods.title"); + String countLabel = TextFormatting.GRAY + "(" + CACHED_MODS.size() + ")"; + String title = modsLabel + " " + countLabel; + int titleWidth = this.fontRenderer.getStringWidth(title); + int titleLeft = this.modList.left + (this.modList.width - titleWidth) / 2; + drawString(this.fontRenderer, title, titleLeft, 10, 0xFFFFFF); + if (ClientHelper.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { this.setActiveTooltip(I18n.format("fml.menu.mods.filterupdates")); this.tooltipYOffset = 10; diff --git a/src/main/resources/assets/forge/lang/en_us.lang b/src/main/resources/assets/forge/lang/en_us.lang index 62452d800..094c2577b 100644 --- a/src/main/resources/assets/forge/lang/en_us.lang +++ b/src/main/resources/assets/forge/lang/en_us.lang @@ -241,6 +241,7 @@ fml.menu.mods.website=Website fml.menu.mods.issue=Submit Bug fml.menu.mods.searchwithdots=Search... fml.menu.mods.noselection=No mod selected... +fml.menu.mods.nomods=No mods... fml.menu.mods.openmodsfolder=Open mods folder fml.menu.mods.filterupdates=Show only mods with updates fml.menu.mods.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! From 31142d936b8a55b678fc61be9868bf7ef7b212c0 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:31:29 +0800 Subject: [PATCH 23/91] Update CatalogueContainer.java --- .../fml/client/modlist/screen/CatalogueContainer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java index f2e228403..e1a9f8916 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java @@ -23,7 +23,6 @@ public CatalogueContainer() { meta.logoBlur = false; meta.iconFile = "/catalogue_icon.png"; meta.iconBlur = false; - meta.iconItem = "minecraft:diamond_sword"; meta.backgroundFile = "/catalogue_background.png"; } } From 4d74274429345cda8ca37546cf0c41ad19ef64de Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:59:25 +0800 Subject: [PATCH 24/91] Update lang, remove mod options keys --- .../client/gui/GuiIngameMenu.java.patch | 2 +- .../client/gui/GuiMainMenu.java.patch | 53 +++++++++++-------- .../screen/CatalogueModListScreen.java | 2 +- .../resources/assets/forge/lang/en_us.lang | 7 ++- .../resources/assets/forge/lang/es_es.lang | 1 - .../resources/assets/forge/lang/fr_fr.lang | 1 - .../resources/assets/forge/lang/ja_jp.lang | 1 - .../resources/assets/forge/lang/ko_kr.lang | 1 - .../resources/assets/forge/lang/ru_ru.lang | 1 - .../resources/assets/forge/lang/zh_cn.lang | 26 +++++++-- .../resources/assets/forge/lang/zh_tw.lang | 1 - 11 files changed, 58 insertions(+), 38 deletions(-) diff --git a/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.java.patch index 00ef0fbab..25d030468 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.java.patch @@ -29,7 +29,7 @@ - GuiButton guibutton = this.func_189646_b( - new GuiButton(7, this.field_146294_l / 2 + 2, this.field_146295_m / 4 + 96 + -16, 98, 20, I18n.func_135052_a("menu.shareToLan")) - ); -+ this.field_146292_n.add(new GuiButton(12, this.field_146294_l / 2 + 2, this.field_146295_m / 4 + 96 + i, 98, 20, I18n.func_135052_a("fml.menu.modoptions"))); ++ this.field_146292_n.add(new GuiButton(12, this.field_146294_l / 2 + 2, this.field_146295_m / 4 + 96 + i, 98, 20, I18n.func_135052_a("fml.menu.mods"))); + GuiButton guibutton = this.func_189646_b(new GuiButton(7, this.field_146294_l / 2 - 100, this.field_146295_m / 4 + 72 + -16, 200, 20, I18n.func_135052_a("menu.shareToLan", new Object[0]))); guibutton.field_146124_l = this.field_146297_k.func_71356_B() && !this.field_146297_k.func_71401_C().func_71344_c(); - this.field_146292_n diff --git a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch index a5c5709c8..a103ebef2 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch @@ -8,15 +8,24 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; -@@ -29,6 +30,7 @@ +@@ -20,8 +21,6 @@ + import net.minecraft.client.renderer.vertex.DefaultVertexFormats; + import net.minecraft.client.resources.I18n; + import net.minecraft.client.resources.IResource; +-import net.minecraft.client.settings.GameSettings; +-import net.minecraft.realms.RealmsBridge; + import net.minecraft.util.ResourceLocation; + import net.minecraft.util.StringUtils; + import net.minecraft.util.math.MathHelper; +@@ -29,6 +28,7 @@ import net.minecraft.world.WorldServerDemo; import net.minecraft.world.storage.ISaveFormat; import net.minecraft.world.storage.WorldInfo; -+import net.minecraftforge.fml.client.TEMPmodlist.screen.CatalogueModListScreen; ++import net.minecraftforge.fml.client.modlist.screen.CatalogueModListScreen; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import org.apache.commons.io.IOUtils; -@@ -57,35 +59,30 @@ +@@ -57,35 +57,30 @@ private int field_92020_v; private int field_92019_w; private String field_92025_p; @@ -58,7 +67,7 @@ iresource = Minecraft.func_71410_x().func_110442_L().func_110536_a(field_110353_x); BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(iresource.func_110527_b(), StandardCharsets.UTF_8)); String s; -@@ -102,19 +99,24 @@ +@@ -102,19 +97,24 @@ if (!list.isEmpty()) { @@ -87,7 +96,7 @@ } this.field_73974_b = field_175374_h.nextFloat(); -@@ -130,30 +132,18 @@ +@@ -130,30 +130,18 @@ private boolean func_183501_a() { @@ -121,7 +130,7 @@ public void func_73866_w_() { this.field_73977_n = new DynamicTexture(256, 256); -@@ -198,40 +188,27 @@ +@@ -198,40 +186,27 @@ this.field_92024_r = this.field_146289_q.func_78256_a(this.field_146972_A); int k = Math.max(this.field_92023_s, this.field_92024_r); this.field_92022_t = (this.field_146294_l - k) / 2; @@ -166,7 +175,7 @@ ISaveFormat isaveformat = this.field_146297_k.func_71359_d(); WorldInfo worldinfo = isaveformat.func_75803_c("Demo_World"); -@@ -241,7 +218,6 @@ +@@ -241,7 +216,6 @@ } } @@ -174,7 +183,7 @@ protected void func_146284_a(GuiButton p_146284_1_) throws IOException { if (p_146284_1_.field_146127_k == 0) -@@ -264,16 +240,16 @@ +@@ -264,16 +238,16 @@ this.field_146297_k.func_147108_a(new GuiMultiplayer(this)); } @@ -196,7 +205,7 @@ if (p_146284_1_.field_146127_k == 11) { this.field_146297_k.func_71371_a("Demo_World", "Demo_World", WorldServerDemo.field_73071_a); -@@ -286,28 +262,15 @@ +@@ -286,28 +260,15 @@ if (worldinfo != null) { @@ -226,7 +235,7 @@ public void func_73878_a(boolean p_73878_1_, int p_73878_2_) { if (p_73878_1_ && p_73878_2_ == 12) -@@ -328,7 +291,7 @@ +@@ -328,7 +289,7 @@ try { Class oclass = Class.forName("java.awt.Desktop"); @@ -235,7 +244,7 @@ oclass.getMethod("browse", URI.class).invoke(object, new URI(this.field_104024_v)); } catch (Throwable throwable) -@@ -359,12 +322,10 @@ +@@ -359,12 +320,10 @@ GlStateManager.func_179118_c(); GlStateManager.func_179129_p(); GlStateManager.func_179132_a(false); @@ -250,7 +259,7 @@ { GlStateManager.func_179094_E(); float f = ((float)(j % 8) / 8.0F - 0.5F) / 64.0F; -@@ -374,7 +335,7 @@ +@@ -374,7 +333,7 @@ GlStateManager.func_179114_b(MathHelper.func_76126_a(this.field_73979_m / 400.0F) * 25.0F + 20.0F, 1.0F, 0.0F, 0.0F); GlStateManager.func_179114_b(-this.field_73979_m * 0.1F, 0.0F, 1.0F, 0.0F); @@ -259,7 +268,7 @@ { GlStateManager.func_179094_E(); -@@ -407,10 +368,10 @@ +@@ -407,10 +366,10 @@ bufferbuilder.func_181668_a(7, DefaultVertexFormats.field_181709_i); int l = 255 / (j + 1); float f3 = 0.0F; @@ -274,7 +283,7 @@ tessellator.func_78381_a(); GlStateManager.func_179121_F(); } -@@ -419,7 +380,7 @@ +@@ -419,7 +378,7 @@ GlStateManager.func_179135_a(true, true, true, false); } @@ -283,7 +292,7 @@ GlStateManager.func_179135_a(true, true, true, true); GlStateManager.func_179128_n(5889); GlStateManager.func_179121_F(); -@@ -437,9 +398,7 @@ +@@ -437,9 +396,7 @@ GlStateManager.func_187421_b(3553, 10240, 9729); GlStateManager.func_187443_a(3553, 0, 0, 0, 0, 0, 256, 256); GlStateManager.func_179147_l(); @@ -294,7 +303,7 @@ GlStateManager.func_179135_a(true, true, true, false); Tessellator tessellator = Tessellator.func_178181_a(); BufferBuilder bufferbuilder = tessellator.func_178180_c(); -@@ -447,28 +406,16 @@ +@@ -447,28 +404,16 @@ GlStateManager.func_179118_c(); int i = 3; @@ -328,7 +337,7 @@ } tessellator.func_78381_a(); -@@ -498,26 +445,13 @@ +@@ -498,26 +443,13 @@ Tessellator tessellator = Tessellator.func_178181_a(); BufferBuilder bufferbuilder = tessellator.func_178180_c(); bufferbuilder.func_181668_a(7, DefaultVertexFormats.field_181709_i); @@ -359,7 +368,7 @@ public void func_73863_a(int p_73863_1_, int p_73863_2_, float p_73863_3_) { this.field_73979_m += p_73863_3_; -@@ -532,7 +466,7 @@ +@@ -532,7 +464,7 @@ this.field_146297_k.func_110434_K().func_110577_a(field_110352_y); GlStateManager.func_179131_c(1.0F, 1.0F, 1.0F, 1.0F); @@ -368,7 +377,7 @@ { this.func_73729_b(j + 0, 30, 0, 0, 99, 44); this.func_73729_b(j + 99, 30, 129, 0, 27, 44); -@@ -548,10 +482,13 @@ +@@ -548,10 +480,13 @@ this.field_146297_k.func_110434_K().func_110577_a(field_194400_H); func_146110_a(j + 88, 67, 0.0F, 0.0F, 98, 14, 128.0F, 16.0F); @@ -383,7 +392,7 @@ f = f * 100.0F / (float)(this.field_146289_q.func_78256_a(this.field_73975_c) + 32); GlStateManager.func_179152_a(f, f, f); this.func_73732_a(this.field_146289_q, this.field_73975_c, 0, -8, -256); -@@ -567,14 +504,19 @@ +@@ -567,14 +502,19 @@ s = s + ("release".equalsIgnoreCase(this.field_146297_k.func_184123_d()) ? "" : "/" + this.field_146297_k.func_184123_d()); } @@ -409,7 +418,7 @@ { func_73734_a(this.field_193979_N, this.field_146295_m - 1, this.field_193979_N + this.field_193978_M, this.field_146295_m, -1); } -@@ -583,32 +525,21 @@ +@@ -583,32 +523,21 @@ { func_73734_a(this.field_92022_t - 2, this.field_92021_u - 2, this.field_92020_v + 2, this.field_92019_w - 1, 1428160512); this.func_73731_b(this.field_146289_q, this.field_92025_p, this.field_92022_t, this.field_92021_u, -1); @@ -445,7 +454,7 @@ { GuiConfirmOpenLink guiconfirmopenlink = new GuiConfirmOpenLink(this, this.field_104024_v, 13, true); guiconfirmopenlink.func_146358_g(); -@@ -616,26 +547,10 @@ +@@ -616,26 +545,10 @@ } } diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java index b962add01..587677cbd 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java @@ -1101,7 +1101,7 @@ private void updateSelectedModList() { private void updateSearchField(String value) { if (value.isEmpty()) { - this.searchTextField.setSuggestion(I18n.format("fml.menu.mods.searchwithdots")); + this.searchTextField.setSuggestion(I18n.format("fml.menu.mods.newsearch")); } else { Optional optional = CACHED_MODS.values().stream().filter(data -> { return data.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); diff --git a/src/main/resources/assets/forge/lang/en_us.lang b/src/main/resources/assets/forge/lang/en_us.lang index 094c2577b..659c1c7e2 100644 --- a/src/main/resources/assets/forge/lang/en_us.lang +++ b/src/main/resources/assets/forge/lang/en_us.lang @@ -239,7 +239,7 @@ fml.menu.mods.title=Mods fml.menu.mods.config=Config fml.menu.mods.website=Website fml.menu.mods.issue=Submit Bug -fml.menu.mods.searchwithdots=Search... +fml.menu.mods.newsearch=Search fml.menu.mods.noselection=No mod selected... fml.menu.mods.nomods=No mods... fml.menu.mods.openmodsfolder=Open mods folder @@ -247,16 +247,15 @@ fml.menu.mods.filterupdates=Show only mods with updates fml.menu.mods.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! fml.menu.mods.info.version=Version: %s fml.menu.mods.info.modid=Mod ID: %s -fml.menu.mods.info.authors=Authors: %s +fml.menu.mods.info.authors=Author(s): %s fml.menu.mods.info.credits=Credits: %s -fml.menu.mods.info.license=License: %s +fml.menu.mods.info.license=License(s): %s fml.menu.mods.info.beta=This version is a beta version fml.menu.mods.info.ahead=This version is ahead of the latest version found (%s) fml.menu.mods.info.updateavailable=Update (%s) Available: %s fml.menu.mods.info.updateavailablenopage=Update (%s) Available fml.menu.mods.info.betaupdateavailable=Beta Update (%s) Available: %s fml.menu.mods.info.betaupdateavailablenopage=Beta Update (%s) Available -fml.menu.modoptions=Mod Options... item.forge.bucketFilled.name=%s Bucket diff --git a/src/main/resources/assets/forge/lang/es_es.lang b/src/main/resources/assets/forge/lang/es_es.lang index 06682c277..2e8260661 100644 --- a/src/main/resources/assets/forge/lang/es_es.lang +++ b/src/main/resources/assets/forge/lang/es_es.lang @@ -203,7 +203,6 @@ forge.controlsgui.alt=ALT + %s fml.menu.mods=Mods fml.menu.mods.normal=Normal fml.menu.mods.search=Buscar\: -fml.menu.modoptions=Opciones de mods... item.forge.bucketFilled.name=Cubo de %s diff --git a/src/main/resources/assets/forge/lang/fr_fr.lang b/src/main/resources/assets/forge/lang/fr_fr.lang index a2391ea17..4fe5f0b43 100644 --- a/src/main/resources/assets/forge/lang/fr_fr.lang +++ b/src/main/resources/assets/forge/lang/fr_fr.lang @@ -203,7 +203,6 @@ forge.controlsgui.alt=ALT + %s fml.menu.mods=Mods fml.menu.mods.normal=Normal fml.menu.mods.search=Rechercher \: -fml.menu.modoptions=Options de mods... item.forge.bucketFilled.name=Seau de %s diff --git a/src/main/resources/assets/forge/lang/ja_jp.lang b/src/main/resources/assets/forge/lang/ja_jp.lang index 93acd988f..33acf3941 100644 --- a/src/main/resources/assets/forge/lang/ja_jp.lang +++ b/src/main/resources/assets/forge/lang/ja_jp.lang @@ -203,7 +203,6 @@ forge.controlsgui.alt=Alt + %s fml.menu.mods=Mod fml.menu.mods.normal=通常 fml.menu.mods.search=検索: -fml.menu.modoptions=Mod 設定… item.forge.bucketFilled.name=%s入りバケツ diff --git a/src/main/resources/assets/forge/lang/ko_kr.lang b/src/main/resources/assets/forge/lang/ko_kr.lang index 5ab39cc2f..6b6cb8308 100644 --- a/src/main/resources/assets/forge/lang/ko_kr.lang +++ b/src/main/resources/assets/forge/lang/ko_kr.lang @@ -203,7 +203,6 @@ forge.controlsgui.alt=알트 + %s fml.menu.mods=모드 fml.menu.mods.normal=일반 fml.menu.mods.search=검색: -fml.menu.modoptions=모드 설정... item.forge.bucketFilled.name=%s 양동이 diff --git a/src/main/resources/assets/forge/lang/ru_ru.lang b/src/main/resources/assets/forge/lang/ru_ru.lang index d4c3c7556..581e4534d 100644 --- a/src/main/resources/assets/forge/lang/ru_ru.lang +++ b/src/main/resources/assets/forge/lang/ru_ru.lang @@ -203,7 +203,6 @@ forge.controlsgui.alt=ALT + %s fml.menu.mods=Моды fml.menu.mods.normal=Обычная fml.menu.mods.search=Поиск\: -fml.menu.modoptions=Настройки модов... item.forge.bucketFilled.name=Ведро %s diff --git a/src/main/resources/assets/forge/lang/zh_cn.lang b/src/main/resources/assets/forge/lang/zh_cn.lang index 55d639586..83594ca7c 100644 --- a/src/main/resources/assets/forge/lang/zh_cn.lang +++ b/src/main/resources/assets/forge/lang/zh_cn.lang @@ -185,7 +185,7 @@ fml.config.sample.title=这只是配置界面的示例,不会保存修改结 fml.configgui.applyGlobally=应用到全局 fml.configgui.confirmRestartMessage=我知道了 fml.configgui.gameRestartRequired=有一个或多个设置需要重新启动 Minecraft 才能生效。 -fml.configgui.gameRestartTitle=需要重新启动Minecraft +fml.configgui.gameRestartTitle=需要重新启动 Minecraft fml.configgui.sampletext=示例文本 fml.configgui.tooltip.addNewEntryAbove=在上面添加新条目 fml.configgui.tooltip.applyGlobally=在全局应用撤消更改或重置为默认值。 @@ -203,9 +203,27 @@ forge.controlsgui.control.mac=CMD + %s forge.controlsgui.alt=ALT + %s fml.menu.mods=模组 -fml.menu.mods.normal=普通 -fml.menu.mods.search=搜索: -fml.menu.modoptions=模组设置 +fml.menu.mods.title=模组 +fml.menu.mods.config=配置 +fml.menu.mods.website=网站 +fml.menu.mods.issue=提交 Bug +fml.menu.mods.newsearch=搜索 +fml.menu.mods.noselection=没有选择模组…… +fml.menu.mods.nomods=没有模组…… +fml.menu.mods.openmodsfolder=打开 Mods 文件夹 +fml.menu.mods.filterupdates=仅显示有更新的模组 +fml.menu.mods.info=此菜单由 Catalogue 提供。单击此处打开此模组的 CurseForge 页面! +fml.menu.mods.info.version=版本:%s +fml.menu.mods.info.modid=Mod ID:%s +fml.menu.mods.info.authors=作者:%s +fml.menu.mods.info.credits=致谢:%s +fml.menu.mods.info.license=许可协议:%s +fml.menu.mods.info.beta=此版本为 Beta 版 +fml.menu.mods.info.ahead=此版本高于搜索到的最新版(%s) +fml.menu.mods.info.updateavailable=更新(%s)可用:%s +fml.menu.mods.info.updateavailablenopage=更新(%s)可用 +fml.menu.mods.info.betaupdateavailable=Beta 更新(%s)可用:%s +fml.menu.mods.info.betaupdateavailablenopage=Beta 更新(%s)可用 item.forge.bucketFilled.name=%s桶 diff --git a/src/main/resources/assets/forge/lang/zh_tw.lang b/src/main/resources/assets/forge/lang/zh_tw.lang index a7c0b41e6..34bd5403d 100644 --- a/src/main/resources/assets/forge/lang/zh_tw.lang +++ b/src/main/resources/assets/forge/lang/zh_tw.lang @@ -203,7 +203,6 @@ forge.controlsgui.alt=ALT + %s fml.menu.mods=模組 fml.menu.mods.normal=一般 fml.menu.mods.search=搜尋: -fml.menu.modoptions=模組選項... item.forge.bucketFilled.name=%s桶 From a4a9c1db5769604d09b12c953cc86e5b11681a11 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Wed, 27 Aug 2025 20:32:19 +0800 Subject: [PATCH 25/91] Fix typo --- src/main/java/net/minecraftforge/fml/common/ModMetadata.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java index be9a4921b..42625a99a 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java +++ b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java @@ -50,7 +50,7 @@ public class ModMetadata public String issueTrackerUrl = ""; public String logoFile = ""; - public boolean logoBlur = true; // Blur the border to make it smooth. True by default. + public boolean logoBlur = true; // Blur the edge of the logo to make it smooth. True by default. public String iconFile = ""; public boolean iconBlur = true; public String iconItem = ""; // Customized item or block as icon. Will not applied when iconFile is valid. From 9e95deb3954a1a3ecdf59fe86b41ef97fc3d4b7e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:52:09 +0800 Subject: [PATCH 26/91] Move path, load the dummy mod Move all the resources and java files to a separate path Load the dummy Catalogue mod, remove links --- .../client/gui/GuiMainMenu.java.patch | 2 +- .../catalogue}/CatalogueContainer.java | 22 ++-- .../com/mrcrayfish/catalogue/Constants.java | 10 ++ .../catalogue/client}/ClientHelper.java | 2 +- .../screen/CatalogueModListScreen.java | 96 +++++++++--------- .../client}/screen/MinecraftContainer.java | 2 +- .../widget/CatalogueCheckBoxButton.java | 8 +- .../screen/widget/CatalogueIconButton.java | 8 +- .../screen/widget/CatalogueListExtended.java | 2 +- .../screen/widget/CatalogueTextField.java | 2 +- .../fml/client/FMLClientHandler.java | 2 +- .../minecraftforge/fml/client/GuiModList.java | 2 +- .../net/minecraftforge/fml/common/Loader.java | 2 + .../catalogue/background.png} | Bin .../catalogue/banner.png} | Bin src/main/resources/assets/catalogue/icon.png | Bin 0 -> 2635 bytes .../assets/catalogue/lang/en_us.lang | 21 ++++ .../assets/catalogue/lang/zh_cn.lang | 21 ++++ .../textures/gui/checkbox.png | Bin .../textures/gui/icons.png | Bin .../textures/gui/minecraft.png | Bin .../textures/gui/missing_background.png | Bin .../textures/gui/missing_banner.png | Bin .../resources/assets/forge/lang/en_us.lang | 21 ---- .../resources/assets/forge/lang/zh_cn.lang | 21 ---- 25 files changed, 131 insertions(+), 113 deletions(-) rename src/main/java/{net/minecraftforge/fml/client/modlist/screen => com/mrcrayfish/catalogue}/CatalogueContainer.java (56%) create mode 100644 src/main/java/com/mrcrayfish/catalogue/Constants.java rename src/main/java/{net/minecraftforge/fml/client/modlist => com/mrcrayfish/catalogue/client}/ClientHelper.java (97%) rename src/main/java/{net/minecraftforge/fml/client/modlist => com/mrcrayfish/catalogue/client}/screen/CatalogueModListScreen.java (93%) rename src/main/java/{net/minecraftforge/fml/client/modlist => com/mrcrayfish/catalogue/client}/screen/MinecraftContainer.java (95%) rename src/main/java/{net/minecraftforge/fml/client/modlist => com/mrcrayfish/catalogue/client}/screen/widget/CatalogueCheckBoxButton.java (89%) rename src/main/java/{net/minecraftforge/fml/client/modlist => com/mrcrayfish/catalogue/client}/screen/widget/CatalogueIconButton.java (90%) rename src/main/java/{net/minecraftforge/fml/client/modlist => com/mrcrayfish/catalogue/client}/screen/widget/CatalogueListExtended.java (99%) rename src/main/java/{net/minecraftforge/fml/client/modlist => com/mrcrayfish/catalogue/client}/screen/widget/CatalogueTextField.java (98%) rename src/main/resources/{catalogue_background.png => assets/catalogue/background.png} (100%) rename src/main/resources/{catalogue_icon.png => assets/catalogue/banner.png} (100%) create mode 100644 src/main/resources/assets/catalogue/icon.png create mode 100644 src/main/resources/assets/catalogue/lang/en_us.lang create mode 100644 src/main/resources/assets/catalogue/lang/zh_cn.lang rename src/main/resources/assets/{forge => catalogue}/textures/gui/checkbox.png (100%) rename src/main/resources/assets/{forge => catalogue}/textures/gui/icons.png (100%) rename src/main/resources/assets/{forge => catalogue}/textures/gui/minecraft.png (100%) rename src/main/resources/assets/{forge => catalogue}/textures/gui/missing_background.png (100%) rename src/main/resources/assets/{forge => catalogue}/textures/gui/missing_banner.png (100%) diff --git a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch index a103ebef2..1322307f8 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch @@ -21,7 +21,7 @@ import net.minecraft.world.WorldServerDemo; import net.minecraft.world.storage.ISaveFormat; import net.minecraft.world.storage.WorldInfo; -+import net.minecraftforge.fml.client.modlist.screen.CatalogueModListScreen; ++import com.mrcrayfish.catalogue.client.screen.CatalogueModListScreen; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import org.apache.commons.io.IOUtils; diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java b/src/main/java/com/mrcrayfish/catalogue/CatalogueContainer.java similarity index 56% rename from src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java rename to src/main/java/com/mrcrayfish/catalogue/CatalogueContainer.java index e1a9f8916..05e7489af 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueContainer.java +++ b/src/main/java/com/mrcrayfish/catalogue/CatalogueContainer.java @@ -1,28 +1,32 @@ -package net.minecraftforge.fml.client.modlist.screen; +package com.mrcrayfish.catalogue; +import com.google.common.eventbus.EventBus; import net.minecraftforge.fml.common.DummyModContainer; +import net.minecraftforge.fml.common.LoadController; import net.minecraftforge.fml.common.ModMetadata; import java.util.Arrays; -// A dummy mod only displayed in list public class CatalogueContainer extends DummyModContainer { public CatalogueContainer() { super(new ModMetadata()); ModMetadata meta = this.getMetadata(); - meta.modId = "catalogue"; - meta.name = "Catalogue"; + meta.modId = Constants.MOD_ID; + meta.name = Constants.MOD_NAME; meta.description = "Updates Forge's mod list with a new and improved design"; meta.version = "1.0.0"; meta.authorList = Arrays.asList("MrCrayfish", "RuiXuqi"); - meta.url = "https://github.com/RuiXuqi/Catalogue-Vintage"; - meta.issueTrackerUrl = "https://github.com/RuiXuqi/Catalogue-Vintage/issues"; meta.credits = "Hatsondogs for creating icons"; meta.license = "MIT"; - meta.logoFile = "/catalogue_icon.png"; + meta.logoFile = "/assets/catalogue/banner.png"; meta.logoBlur = false; - meta.iconFile = "/catalogue_icon.png"; + meta.iconFile = "/assets/catalogue/icon.png"; meta.iconBlur = false; - meta.backgroundFile = "/catalogue_background.png"; + meta.backgroundFile = "/assets/catalogue/background.png"; + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + return true; } } diff --git a/src/main/java/com/mrcrayfish/catalogue/Constants.java b/src/main/java/com/mrcrayfish/catalogue/Constants.java new file mode 100644 index 000000000..cc788657e --- /dev/null +++ b/src/main/java/com/mrcrayfish/catalogue/Constants.java @@ -0,0 +1,10 @@ +package com.mrcrayfish.catalogue; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Constants { + public static final String MOD_ID = "catalogue"; + public static final String MOD_NAME = "Catalogue"; + public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME); +} diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/ClientHelper.java b/src/main/java/com/mrcrayfish/catalogue/client/ClientHelper.java similarity index 97% rename from src/main/java/net/minecraftforge/fml/client/modlist/ClientHelper.java rename to src/main/java/com/mrcrayfish/catalogue/client/ClientHelper.java index 1caed7d0a..bd3b3a74e 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/ClientHelper.java +++ b/src/main/java/com/mrcrayfish/catalogue/client/ClientHelper.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.modlist; +package com.mrcrayfish.catalogue.client; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java b/src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java similarity index 93% rename from src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java rename to src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java index 587677cbd..8070fd68f 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/CatalogueModListScreen.java +++ b/src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java @@ -1,6 +1,7 @@ -package net.minecraftforge.fml.client.modlist.screen; +package com.mrcrayfish.catalogue.client.screen; import com.google.common.collect.Lists; +import com.mrcrayfish.catalogue.Constants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiOptions; @@ -26,12 +27,11 @@ import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.client.IModGuiFactory; -import net.minecraftforge.fml.client.modlist.ClientHelper; -import net.minecraftforge.fml.client.modlist.screen.widget.CatalogueCheckBoxButton; -import net.minecraftforge.fml.client.modlist.screen.widget.CatalogueIconButton; -import net.minecraftforge.fml.client.modlist.screen.widget.CatalogueListExtended; -import net.minecraftforge.fml.client.modlist.screen.widget.CatalogueTextField; -import net.minecraftforge.fml.common.FMLLog; +import com.mrcrayfish.catalogue.client.ClientHelper; +import com.mrcrayfish.catalogue.client.screen.widget.CatalogueCheckBoxButton; +import com.mrcrayfish.catalogue.client.screen.widget.CatalogueIconButton; +import com.mrcrayfish.catalogue.client.screen.widget.CatalogueListExtended; +import com.mrcrayfish.catalogue.client.screen.widget.CatalogueTextField; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.ModMetadata; @@ -48,10 +48,10 @@ import java.util.stream.Collectors; public class CatalogueModListScreen extends GuiScreen { - private static final ResourceLocation MISSING_BANNER = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/missing_banner.png"); - private static final ResourceLocation MISSING_BACKGROUND = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/missing_background.png"); + private static final ResourceLocation MISSING_BANNER = new ResourceLocation(Constants.MOD_ID, "textures/gui/missing_banner.png"); + private static final ResourceLocation MISSING_BACKGROUND = new ResourceLocation(Constants.MOD_ID, "textures/gui/missing_background.png"); private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); - private static final ResourceLocation MINECRAFT_LOGO = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/minecraft.png"); + private static final ResourceLocation MINECRAFT_LOGO = new ResourceLocation(Constants.MOD_ID, "textures/gui/minecraft.png"); private static final ImageInfo MISSING_BANNER_INFO = new ImageInfo(MISSING_BANNER, new Dimension(120, 120)); private static final Map BANNER_CACHE = new HashMap<>(); private static final Map IMAGE_ICON_CACHE = new HashMap<>(); @@ -80,10 +80,9 @@ public class CatalogueModListScreen extends GuiScreen { public CatalogueModListScreen(GuiScreen parent) { super(); this.parentScreen = parent; - if(!loaded) { + if (!loaded) { Loader.instance().getActiveModList().forEach(data -> CACHED_MODS.put(data.getModId(), data)); CACHED_MODS.put("minecraft", new MinecraftContainer()); // Override minecraft - CACHED_MODS.put("catalogue", new CatalogueContainer()); BANNER_CACHE.put("minecraft", new ImageInfo(MINECRAFT_LOGO, new Dimension(1024, 256))); loaded = true; } @@ -109,13 +108,13 @@ public void initGui() { int contentWidth = this.width - contentLeft - padding; int buttonWidth = (contentWidth - padding) / 3; - this.configButton = this.addButton(new CatalogueIconButton(3, contentLeft, 105, 10, 0, buttonWidth, I18n.format("fml.menu.mods.config"))); + this.configButton = this.addButton(new CatalogueIconButton(3, contentLeft, 105, 10, 0, buttonWidth, I18n.format("catalogue.gui.config"))); this.configButton.visible = false; - this.websiteButton = this.addButton(new CatalogueIconButton(4, contentLeft + buttonWidth + 5, 105, 20, 0, buttonWidth, I18n.format("fml.menu.mods.website"))); + this.websiteButton = this.addButton(new CatalogueIconButton(4, contentLeft + buttonWidth + 5, 105, 20, 0, buttonWidth, I18n.format("catalogue.gui.website"))); this.websiteButton.visible = false; - this.issueButton = this.addButton(new CatalogueIconButton(5, contentLeft + buttonWidth + buttonWidth + 10, 105, 30, 0, buttonWidth, I18n.format("fml.menu.mods.issue"))); + this.issueButton = this.addButton(new CatalogueIconButton(5, contentLeft + buttonWidth + buttonWidth + 10, 105, 30, 0, buttonWidth, I18n.format("catalogue.gui.issue"))); this.issueButton.visible = false; this.descriptionList = new StringList(contentWidth + padding * 2, 50, contentLeft - padding, 130); @@ -147,7 +146,7 @@ public void actionPerformed(GuiButton button) { try { Desktop.getDesktop().open(Loader.instance().getConfigDir().getParentFile().toPath().resolve("mods").toFile()); } catch (Exception e) { - FMLLog.log.error("Problem opening mods folder", e); + Constants.LOG.error("Problem opening mods folder", e); } break; case 3: @@ -157,7 +156,7 @@ public void actionPerformed(GuiButton button) { GuiScreen newScreen = guiFactory.createConfigGui(this); this.mc.displayGuiScreen(newScreen); } catch (Exception e) { - FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", this.selectedModData.getModId(), e); + Constants.LOG.error("There was a critical issue trying to build the config GUI for {}", this.selectedModData.getModId(), e); } } else { this.mc.displayGuiScreen(new GuiOptions(this, this.mc.gameSettings)); @@ -184,9 +183,9 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawModInfo(mouseX, mouseY, partialTicks); super.drawScreen(mouseX, mouseY, partialTicks); - Optional optional = Optional.ofNullable(CACHED_MODS.get("catalogue")); + Optional optional = Optional.ofNullable(CACHED_MODS.get(Constants.MOD_ID)); optional.ifPresent(this::loadAndCacheLogo); - ImageInfo imageInfo = BANNER_CACHE.get("catalogue"); + ImageInfo imageInfo = BANNER_CACHE.get(Constants.MOD_ID); if (imageInfo != null) { Dimension size = imageInfo.size(); this.mc.getTextureManager().bindTexture(imageInfo.resource()); @@ -194,7 +193,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { } if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("fml.menu.mods.info")); + this.setActiveTooltip(I18n.format("catalogue.gui.info")); this.tooltipYOffset = 10; } @@ -225,7 +224,7 @@ protected void keyTyped(char typedChar, int key) throws IOException { @Override public void handleKeyboardInput() throws IOException { if (Keyboard.getEventKey() == Keyboard.KEY_F && (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL))) { - if(!this.searchTextField.isFocused()) { + if (!this.searchTextField.isFocused()) { this.searchTextField.setFocused(true); } return; @@ -249,7 +248,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { GL11.glDisable(GL11.GL_SCISSOR_TEST); if (this.entries.isEmpty()) { - String text = I18n.format("fml.menu.mods.nomods"); + String text = I18n.format("catalogue.gui.no_mods"); int left = this.left + this.width / 2; int top = this.top + (this.bottom - this.top - CatalogueModListScreen.this.fontRenderer.FONT_HEIGHT) / 2; drawCenteredString(CatalogueModListScreen.this.fontRenderer, text, left, top, 0xFFFFFFFF); @@ -390,7 +389,7 @@ private void drawIcon(int top, int left) { RenderHelper.disableStandardItemLighting(); } catch (Exception e) { // Attempt to catch exceptions when rendering item. Sometime level instance isn't checked for null - FMLLog.log.debug("Failed to draw icon for mod '{}'", this.data.getModId()); + Constants.LOG.debug("Failed to draw icon for mod '{}'", this.data.getModId()); ITEM_ICON_CACHE.put(this.data.getModId(), new ItemStack(Blocks.GRASS)); this.icon = new ItemStack(Blocks.GRASS); } @@ -435,7 +434,8 @@ private ItemStack getItemIcon() { ITEM_ICON_CACHE.put(this.data.getModId(), itemStack); return itemStack; } - } catch (Exception ignored) {} + } catch (Exception ignored) { + } } } @@ -513,7 +513,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { } @Override - protected void drawContainerBackground(Tessellator tessellator) { + protected void drawContainerBackground(@Nullable Tessellator tessellator) { int x = this.left; int y = this.top; int width = this.width; @@ -612,7 +612,7 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti // Version check button if (this.selectedModData != null) { int contentLeft = this.modList.right + 12 + 10; - String version = I18n.format("fml.menu.mods.info.version", this.selectedModData.getDisplayVersion()); + String version = I18n.format("catalogue.gui.version", this.selectedModData.getDisplayVersion()); int versionWidth = this.fontRenderer.getStringWidth(version); if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); @@ -676,7 +676,7 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { this.modList.drawScreen(mouseX, mouseY, partialTicks); this.searchTextField.drawTextBox(); - String modsLabel = TextFormatting.BOLD + I18n.format("fml.menu.mods.title"); + String modsLabel = TextFormatting.BOLD + I18n.format("catalogue.gui.title"); String countLabel = TextFormatting.GRAY + "(" + CACHED_MODS.size() + ")"; String title = modsLabel + " " + countLabel; int titleWidth = this.fontRenderer.getStringWidth(title); @@ -684,7 +684,7 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { drawString(this.fontRenderer, title, titleLeft, 10, 0xFFFFFF); if (ClientHelper.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("fml.menu.mods.filterupdates")); + this.setActiveTooltip(I18n.format("catalogue.gui.filter_updates")); this.tooltipYOffset = 10; } } @@ -718,16 +718,16 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { GlStateManager.popMatrix(); // Draw version - String modId = TextFormatting.DARK_GRAY + I18n.format("fml.menu.mods.info.modid", this.selectedModData.getModId()); + String modId = TextFormatting.DARK_GRAY + I18n.format("catalogue.gui.mod_id", this.selectedModData.getModId()); int modIdWidth = this.fontRenderer.getStringWidth(modId); drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); // Draw version String displayVersion = this.selectedModData.getDisplayVersion(); - this.drawStringWithLabel("fml.menu.mods.info.version", displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + this.drawStringWithLabel("catalogue.gui.version", displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); // Draw inner version if the display version is different from it - int versionWidth = this.fontRenderer.getStringWidth(I18n.format("fml.menu.mods.info.version", displayVersion)); + int versionWidth = this.fontRenderer.getStringWidth(I18n.format("catalogue.gui.version", displayVersion)); String innerVersion = this.selectedModData.getVersion(); if (!displayVersion.equals(innerVersion) && ClientHelper.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { this.setActiveTooltip(innerVersion); @@ -743,23 +743,23 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { switch (update.status) { case BETA: - this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.beta")); + this.setActiveTooltip(TextFormatting.GOLD + I18n.format("catalogue.gui.beta")); break; case AHEAD: - this.setActiveTooltip(TextFormatting.LIGHT_PURPLE + I18n.format("fml.menu.mods.info.ahead", update.latestFound)); + this.setActiveTooltip(TextFormatting.LIGHT_PURPLE + I18n.format("catalogue.gui.ahead", update.latestFound)); break; case BETA_OUTDATED: if (update.homepage != null) { - this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.betaupdateavailable", update.latestFound, update.homepage)); + this.setActiveTooltip(TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available", update.latestFound, update.homepage)); } else { - this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.betaupdateavailablenopage", update.latestFound)); + this.setActiveTooltip(TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available_no_page", update.latestFound)); } break; case OUTDATED: if (update.homepage != null) { - this.setActiveTooltip(TextFormatting.GREEN + I18n.format("fml.menu.mods.info.updateavailable", update.latestFound, update.homepage)); + this.setActiveTooltip(TextFormatting.GREEN + I18n.format("catalogue.gui.update_available", update.latestFound, update.homepage)); } else { - this.setActiveTooltip(TextFormatting.GREEN + I18n.format("fml.menu.mods.info.updateavailablenopage", update.latestFound)); + this.setActiveTooltip(TextFormatting.GREEN + I18n.format("catalogue.gui.update_available_no_page", update.latestFound)); } break; } @@ -777,25 +777,25 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { // Draw license String license = metadata.license; if (!license.isBlank()) { - this.drawStringWithLabel("fml.menu.mods.info.license", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + this.drawStringWithLabel("catalogue.gui.license", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); labelOffset -= 15; } // Draw credits String credits = metadata.credits; if (!credits.isBlank()) { - this.drawStringWithLabel("fml.menu.mods.info.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + this.drawStringWithLabel("catalogue.gui.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); labelOffset -= 15; } // Draw authors String authors = metadata.getAuthorList(); if (!authors.isBlank()) { - this.drawStringWithLabel("fml.menu.mods.info.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + this.drawStringWithLabel("catalogue.gui.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); } } } else { - String message = TextFormatting.GRAY + I18n.format("fml.menu.mods.noselection"); + String message = TextFormatting.GRAY + I18n.format("catalogue.gui.no_selection"); drawCenteredString(this.fontRenderer, message, contentLeft + contentWidth / 2, this.height / 2 - 5, 0xFFFFFF); } } @@ -940,7 +940,7 @@ private void loadAndCacheBackground(ModContainer data) { ModMetadata metadata = data.getMetadata(); if (metadata == null) return; String background = metadata.backgroundFile; - if(background.isBlank()) return; + if (background.isBlank()) return; if (!background.startsWith("/")) { background = "/" + background; @@ -952,7 +952,7 @@ private void loadAndCacheBackground(ModContainer data) { if (image == null || image.getWidth() != 512 || image.getHeight() != 256) return; TextureManager textureManager = this.mc.getTextureManager(); cachedBackground = textureManager.getDynamicTextureLocation("cataloguebackground", this.createLogoTexture(image, false)); - } catch(IOException ignored) { + } catch (IOException ignored) { } } @@ -967,7 +967,7 @@ public void updateDynamicTexture() { @SuppressWarnings("SameParameterValue") private void drawBackground(int contentWidth, int x, int y) { - if(this.selectedModData == null) return; + if (this.selectedModData == null) return; ResourceLocation texture = cachedBackground != null ? cachedBackground : MISSING_BACKGROUND; this.mc.getTextureManager().bindTexture(texture); @@ -1101,7 +1101,7 @@ private void updateSelectedModList() { private void updateSearchField(String value) { if (value.isEmpty()) { - this.searchTextField.setSuggestion(I18n.format("fml.menu.mods.newsearch")); + this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search")); } else { Optional optional = CACHED_MODS.values().stream().filter(data -> { return data.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); @@ -1139,9 +1139,11 @@ private void openLink(String url) { this.handleComponentClick(new TextComponentString("").setStyle(style)); } - private record Dimension(int width, int height) {} + private record Dimension(int width, int height) { + } - private record ImageInfo(ResourceLocation resource, Dimension size) {} + private record ImageInfo(ResourceLocation resource, Dimension size) { + } private boolean shouldDraw(ForgeVersion.CheckResult update) { return update != null && update.status.shouldDraw(); diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/MinecraftContainer.java b/src/main/java/com/mrcrayfish/catalogue/client/screen/MinecraftContainer.java similarity index 95% rename from src/main/java/net/minecraftforge/fml/client/modlist/screen/MinecraftContainer.java rename to src/main/java/com/mrcrayfish/catalogue/client/screen/MinecraftContainer.java index ba02f00b5..a76b2e007 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/MinecraftContainer.java +++ b/src/main/java/com/mrcrayfish/catalogue/client/screen/MinecraftContainer.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.modlist.screen; +package com.mrcrayfish.catalogue.client.screen; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.common.DummyModContainer; diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueCheckBoxButton.java similarity index 89% rename from src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueCheckBoxButton.java rename to src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueCheckBoxButton.java index 84a9ad702..74693373b 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueCheckBoxButton.java +++ b/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueCheckBoxButton.java @@ -1,17 +1,17 @@ -package net.minecraftforge.fml.client.modlist.screen.widget; +package com.mrcrayfish.catalogue.client.screen.widget; +import com.mrcrayfish.catalogue.Constants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.client.modlist.ClientHelper; +import com.mrcrayfish.catalogue.client.ClientHelper; /** * Author: MrCrayfish */ public class CatalogueCheckBoxButton extends GuiButton { - private static final ResourceLocation TEXTURE = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/checkbox.png"); + private static final ResourceLocation TEXTURE = new ResourceLocation(Constants.MOD_ID, "textures/gui/checkbox.png"); private boolean selected; public CatalogueCheckBoxButton(int id, int x, int y, boolean selectedDefault) { diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueIconButton.java b/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueIconButton.java similarity index 90% rename from src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueIconButton.java rename to src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueIconButton.java index 374deab45..27587353e 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueIconButton.java @@ -1,19 +1,19 @@ -package net.minecraftforge.fml.client.modlist.screen.widget; +package com.mrcrayfish.catalogue.client.screen.widget; +import com.mrcrayfish.catalogue.Constants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.client.modlist.ClientHelper; +import com.mrcrayfish.catalogue.client.ClientHelper; /** * Author: MrCrayfish */ public class CatalogueIconButton extends GuiButton { - private static final ResourceLocation TEXTURE = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/icons.png"); + private static final ResourceLocation TEXTURE = new ResourceLocation(Constants.MOD_ID, "textures/gui/icons.png"); private final String label; private final int u, v; diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueListExtended.java b/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueListExtended.java similarity index 99% rename from src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueListExtended.java rename to src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueListExtended.java index 93b4e4943..3c6e3bb5a 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueListExtended.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.modlist.screen.widget; +package com.mrcrayfish.catalogue.client.screen.widget; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiListExtended; diff --git a/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueTextField.java b/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueTextField.java similarity index 98% rename from src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueTextField.java rename to src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueTextField.java index f27330250..3afd85c42 100644 --- a/src/main/java/net/minecraftforge/fml/client/modlist/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueTextField.java @@ -1,4 +1,4 @@ -package net.minecraftforge.fml.client.modlist.screen.widget; +package com.mrcrayfish.catalogue.client.screen.widget; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; diff --git a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java index c5a8e42e9..e66fdd4b9 100644 --- a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java +++ b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java @@ -91,7 +91,7 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.common.util.CompoundDataFixer; -import net.minecraftforge.fml.client.modlist.screen.CatalogueModListScreen; +import com.mrcrayfish.catalogue.client.screen.CatalogueModListScreen; import net.minecraftforge.fml.client.registry.RenderingRegistry; import net.minecraftforge.fml.common.DummyModContainer; import net.minecraftforge.fml.common.DuplicateModsFoundException; diff --git a/src/main/java/net/minecraftforge/fml/client/GuiModList.java b/src/main/java/net/minecraftforge/fml/client/GuiModList.java index 21f05f4c7..ac5198c95 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiModList.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiModList.java @@ -1,7 +1,7 @@ package net.minecraftforge.fml.client; import net.minecraft.client.gui.GuiScreen; -import net.minecraftforge.fml.client.modlist.screen.CatalogueModListScreen; +import com.mrcrayfish.catalogue.client.screen.CatalogueModListScreen; @Deprecated public class GuiModList extends CatalogueModListScreen { diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index b06d37ce3..55a127cf4 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -40,6 +40,7 @@ import com.cleanroommc.common.CleanroomContainer; import com.cleanroommc.common.MixinContainer; import com.cleanroommc.common.ConfigAnytimeContainer; +import com.mrcrayfish.catalogue.CatalogueContainer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.common.capabilities.CapabilityManager; @@ -377,6 +378,7 @@ private ModDiscoverer identifyMods(List additionalContainers) mods.add(new InjectedModContainer(new CleanroomContainer(), FMLSanityChecker.fmlLocation)); mods.add(new InjectedModContainer(new MixinContainer(), FMLSanityChecker.fmlLocation)); mods.add(new InjectedModContainer(new ConfigAnytimeContainer(), FMLSanityChecker.fmlLocation)); + mods.add(new InjectedModContainer(new CatalogueContainer(), FMLSanityChecker.fmlLocation)); for (String cont : injectedContainers) { diff --git a/src/main/resources/catalogue_background.png b/src/main/resources/assets/catalogue/background.png similarity index 100% rename from src/main/resources/catalogue_background.png rename to src/main/resources/assets/catalogue/background.png diff --git a/src/main/resources/catalogue_icon.png b/src/main/resources/assets/catalogue/banner.png similarity index 100% rename from src/main/resources/catalogue_icon.png rename to src/main/resources/assets/catalogue/banner.png diff --git a/src/main/resources/assets/catalogue/icon.png b/src/main/resources/assets/catalogue/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ab238d3ac1e06762c7d4ff40b1c88453dded6894 GIT binary patch literal 2635 zcmcImZHQD=7`~%xyDG|xCMMBduJrAh`#JXu$K7>i+Z|}uWgYV4hji{a=g!`onLFKk zXJ^(579lVUtRGpJf`(xbX=Owpq)8BlmWr@`ELe2ze7UZ>+afe@&wK8< z?|I(m{W@n}80ha^vS{@phGCYZ`jUfG9txkfyXd&*d=^l#u-LcNXPCvy!zaS*Kd_2n z+Bhe(CD@XF+(d3Z3bAXEXeD2y+6=Rd;2MJh0*Km zIej}lvT0|32JPH|49s<|XV+Cs3XmrOWGnex!8a>$u8M2YdAQ7TY*i)L5$C!?gY1^{ z0Gn_IBu8Kze1e5VEWGb2U94-(%kd5H83 zlR~hjVXQdh2ChHk&cLnt`4)y4H4PR^UalG*jChhGd5Y~*U9rlqXeK-Y1J}#AZmyB2 zfrdpkkq8rGun%o^3fL|C4~L}cdq@%nB+f~KBmzMJf|L<8QxwdY_9z`?p{AOqrGQ}o zCn<{}5cN=55=~jWMcKuUJu(;7blOZ6`~VgZNhRYPB^7lXY}yzbM6q>%F%*EIDI(Af zfesQTgh(p1WVOnR?oYa?6b2*QU#nc~B8t%rsA)qL+Ss-zU0noNQY>HyN(>-kX|Wh0 zatvt=w2huan+N75qlT<7MKmlShL9}-NGLT)QB+{)$O4oMf;JIk0~#EA({94exnA1E zG`jNS^;9A;;JLPwqaXai-VJQ3C!valssbr0ax6xs?Rt4gYt6~SEa8iVY*_h}26ea3 z@oAbz>bCFs#>ecRiw?PHk8 z`BbtyQ~B=K@84T^rfbFMmF4$#9XP%HlVu;A2o{{3x9gi@m+#xRPdx@--`;-rrrwru zW#jJNkB`5y=E`rsjyvoc5&g$?9s2@`1`4q+d9khf8}I-0+uEsEb_jIUX5qp= z_n+Q=HL~ZYx4xbj-FxxP3$4$dymV;_a&=pMcc*ndvX@+2@Y(pVc5Up`QGfm}W*OVo z9=Tl{S$eMZ#nnq&w|2ZcF$?0UbFH5)X|k)EX^N{IhbBhvkvl@>&Ba^HU4C_9^rOfO z9NJ#GF<gi7&+wkPBe*rSRIO6~S literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang new file mode 100644 index 000000000..55ecde2a0 --- /dev/null +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -0,0 +1,21 @@ +catalogue.gui.title=Mods +catalogue.gui.config=Config +catalogue.gui.website=Website +catalogue.gui.issue=Submit Bug +catalogue.gui.search=Search +catalogue.gui.no_selection=No mod selected... +catalogue.gui.no_mods=No mods... +catalogue.gui.open_mods_folder=Open mods folder +catalogue.gui.filter_updates=Show only mods with updates +catalogue.gui.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! +catalogue.gui.version=Version: %s +catalogue.gui.mod_id=Mod ID: %s +catalogue.gui.authors=Author(s): %s +catalogue.gui.credits=Credits: %s +catalogue.gui.license=License(s): %s +catalogue.gui.beta=This version is a beta version +catalogue.gui.ahead=This version is ahead of the latest version found (%s) +catalogue.gui.update_available=Update (%s) Available: %s +catalogue.gui.update_available_no_page=Update (%s) Available +catalogue.gui.beta_update_available=Beta Update (%s) Available: %s +catalogue.gui.beta_update_available_no_page=Beta Update (%s) Available \ No newline at end of file diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang new file mode 100644 index 000000000..22decae29 --- /dev/null +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -0,0 +1,21 @@ +catalogue.gui.title=模组 +catalogue.gui.config=配置 +catalogue.gui.website=网站 +catalogue.gui.issue=提交 Bug +catalogue.gui.search=搜索 +catalogue.gui.no_selection=没有选择模组…… +catalogue.gui.no_mods=没有模组… +catalogue.gui.open_mods_folder=打开 Mods 文件夹 +catalogue.gui.filter_updates=仅显示有更新的模组 +catalogue.gui.info=此菜单由 Catalogue 提供。单击此处打开此模组的 CurseForge 页面! +catalogue.gui.version=版本:%s +catalogue.gui.mod_id=Mod ID:%s +catalogue.gui.authors=作者:%s +catalogue.gui.credits=致谢:%s +catalogue.gui.license=许可协议:%s +catalogue.gui.beta=此版本为 Beta 版 +catalogue.gui.ahead=此版本高于搜索到的最新版(%s) +catalogue.gui.update_available=更新(%s)可用:%s +catalogue.gui.update_available_no_page=更新(%s)可用 +catalogue.gui.beta_update_available=Beta 更新(%s)可用:%s +catalogue.gui.beta_update_available_no_page=Beta 更新(%s)可用 \ No newline at end of file diff --git a/src/main/resources/assets/forge/textures/gui/checkbox.png b/src/main/resources/assets/catalogue/textures/gui/checkbox.png similarity index 100% rename from src/main/resources/assets/forge/textures/gui/checkbox.png rename to src/main/resources/assets/catalogue/textures/gui/checkbox.png diff --git a/src/main/resources/assets/forge/textures/gui/icons.png b/src/main/resources/assets/catalogue/textures/gui/icons.png similarity index 100% rename from src/main/resources/assets/forge/textures/gui/icons.png rename to src/main/resources/assets/catalogue/textures/gui/icons.png diff --git a/src/main/resources/assets/forge/textures/gui/minecraft.png b/src/main/resources/assets/catalogue/textures/gui/minecraft.png similarity index 100% rename from src/main/resources/assets/forge/textures/gui/minecraft.png rename to src/main/resources/assets/catalogue/textures/gui/minecraft.png diff --git a/src/main/resources/assets/forge/textures/gui/missing_background.png b/src/main/resources/assets/catalogue/textures/gui/missing_background.png similarity index 100% rename from src/main/resources/assets/forge/textures/gui/missing_background.png rename to src/main/resources/assets/catalogue/textures/gui/missing_background.png diff --git a/src/main/resources/assets/forge/textures/gui/missing_banner.png b/src/main/resources/assets/catalogue/textures/gui/missing_banner.png similarity index 100% rename from src/main/resources/assets/forge/textures/gui/missing_banner.png rename to src/main/resources/assets/catalogue/textures/gui/missing_banner.png diff --git a/src/main/resources/assets/forge/lang/en_us.lang b/src/main/resources/assets/forge/lang/en_us.lang index 659c1c7e2..c86a8a603 100644 --- a/src/main/resources/assets/forge/lang/en_us.lang +++ b/src/main/resources/assets/forge/lang/en_us.lang @@ -235,27 +235,6 @@ forge.controlsgui.control.mac=CMD + %s forge.controlsgui.alt=ALT + %s fml.menu.mods=Mods -fml.menu.mods.title=Mods -fml.menu.mods.config=Config -fml.menu.mods.website=Website -fml.menu.mods.issue=Submit Bug -fml.menu.mods.newsearch=Search -fml.menu.mods.noselection=No mod selected... -fml.menu.mods.nomods=No mods... -fml.menu.mods.openmodsfolder=Open mods folder -fml.menu.mods.filterupdates=Show only mods with updates -fml.menu.mods.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! -fml.menu.mods.info.version=Version: %s -fml.menu.mods.info.modid=Mod ID: %s -fml.menu.mods.info.authors=Author(s): %s -fml.menu.mods.info.credits=Credits: %s -fml.menu.mods.info.license=License(s): %s -fml.menu.mods.info.beta=This version is a beta version -fml.menu.mods.info.ahead=This version is ahead of the latest version found (%s) -fml.menu.mods.info.updateavailable=Update (%s) Available: %s -fml.menu.mods.info.updateavailablenopage=Update (%s) Available -fml.menu.mods.info.betaupdateavailable=Beta Update (%s) Available: %s -fml.menu.mods.info.betaupdateavailablenopage=Beta Update (%s) Available item.forge.bucketFilled.name=%s Bucket diff --git a/src/main/resources/assets/forge/lang/zh_cn.lang b/src/main/resources/assets/forge/lang/zh_cn.lang index 83594ca7c..f2c566197 100644 --- a/src/main/resources/assets/forge/lang/zh_cn.lang +++ b/src/main/resources/assets/forge/lang/zh_cn.lang @@ -203,27 +203,6 @@ forge.controlsgui.control.mac=CMD + %s forge.controlsgui.alt=ALT + %s fml.menu.mods=模组 -fml.menu.mods.title=模组 -fml.menu.mods.config=配置 -fml.menu.mods.website=网站 -fml.menu.mods.issue=提交 Bug -fml.menu.mods.newsearch=搜索 -fml.menu.mods.noselection=没有选择模组…… -fml.menu.mods.nomods=没有模组…… -fml.menu.mods.openmodsfolder=打开 Mods 文件夹 -fml.menu.mods.filterupdates=仅显示有更新的模组 -fml.menu.mods.info=此菜单由 Catalogue 提供。单击此处打开此模组的 CurseForge 页面! -fml.menu.mods.info.version=版本:%s -fml.menu.mods.info.modid=Mod ID:%s -fml.menu.mods.info.authors=作者:%s -fml.menu.mods.info.credits=致谢:%s -fml.menu.mods.info.license=许可协议:%s -fml.menu.mods.info.beta=此版本为 Beta 版 -fml.menu.mods.info.ahead=此版本高于搜索到的最新版(%s) -fml.menu.mods.info.updateavailable=更新(%s)可用:%s -fml.menu.mods.info.updateavailablenopage=更新(%s)可用 -fml.menu.mods.info.betaupdateavailable=Beta 更新(%s)可用:%s -fml.menu.mods.info.betaupdateavailablenopage=Beta 更新(%s)可用 item.forge.bucketFilled.name=%s桶 From 74e38dc6e2084435af69f7493ef085f4b963ee1e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:20:07 +0800 Subject: [PATCH 27/91] Fix crash with CoFH mods world is null --- .../client/screen/CatalogueModListScreen.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java index 8070fd68f..901903bfd 100644 --- a/src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java @@ -140,7 +140,7 @@ public void initGui() { public void actionPerformed(GuiButton button) { switch (button.id) { case 1: - mc.displayGuiScreen(parentScreen); + this.mc.displayGuiScreen(parentScreen); break; case 2: try { @@ -389,7 +389,7 @@ private void drawIcon(int top, int left) { RenderHelper.disableStandardItemLighting(); } catch (Exception e) { // Attempt to catch exceptions when rendering item. Sometime level instance isn't checked for null - Constants.LOG.debug("Failed to draw icon for mod '{}'", this.data.getModId()); + Constants.LOG.debug("Failed to draw icon for mod '{}'", this.data.getModId(), e); ITEM_ICON_CACHE.put(this.data.getModId(), new ItemStack(Blocks.GRASS)); this.icon = new ItemStack(Blocks.GRASS); } @@ -434,7 +434,8 @@ private ItemStack getItemIcon() { ITEM_ICON_CACHE.put(this.data.getModId(), itemStack); return itemStack; } - } catch (Exception ignored) { + } catch (Exception e) { + Constants.LOG.debug("Failed to get customized item icon for mod '{}'", this.data.getModId(), e); } } } @@ -446,9 +447,13 @@ private ItemStack getItemIcon() { if (!item.isEmpty()) { // If the item is in a creative tab, Catalogue will use the tab's icon if (item.getItem().getCreativeTab() != null) { - ItemStack tabItem = item.getItem().getCreativeTab().getIcon(); - if (tabItem != null && !tabItem.isEmpty() && tabItem.getItem().getRegistryName().getNamespace().equals(this.data.getModId())) { - item = tabItem; + try { + ItemStack tabItem = item.getItem().getCreativeTab().getIcon(); + if (tabItem != null && !tabItem.isEmpty() && tabItem.getItem().getRegistryName().getNamespace().equals(this.data.getModId())) { + item = tabItem; + } + } catch (Exception e) { + Constants.LOG.debug("Failed to get creative tab icon for mod '{}'", this.data.getModId(), e); } } ITEM_ICON_CACHE.put(this.data.getModId(), item); From d938889b465cdd7de762e7d4591c6b70fb786106 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:55:09 +0800 Subject: [PATCH 28/91] Change root package --- .../catalogue/Constants.java | 2 +- .../catalogue/client/ClientHelper.java | 2 +- .../client/screen/CatalogueModListScreen.java | 14 +++++++------- .../client/screen/MinecraftContainer.java | 2 +- .../screen/widget/CatalogueCheckBoxButton.java | 6 +++--- .../client/screen/widget/CatalogueIconButton.java | 6 +++--- .../screen/widget/CatalogueListExtended.java | 2 +- .../client/screen/widget/CatalogueTextField.java | 2 +- .../common}/CatalogueContainer.java | 3 ++- .../fml/client/FMLClientHandler.java | 2 +- .../net/minecraftforge/fml/client/GuiModList.java | 2 +- .../java/net/minecraftforge/fml/common/Loader.java | 2 +- 12 files changed, 23 insertions(+), 22 deletions(-) rename src/main/java/com/{mrcrayfish => cleanroommc}/catalogue/Constants.java (88%) rename src/main/java/com/{mrcrayfish => cleanroommc}/catalogue/client/ClientHelper.java (97%) rename src/main/java/com/{mrcrayfish => cleanroommc}/catalogue/client/screen/CatalogueModListScreen.java (99%) rename src/main/java/com/{mrcrayfish => cleanroommc}/catalogue/client/screen/MinecraftContainer.java (95%) rename src/main/java/com/{mrcrayfish => cleanroommc}/catalogue/client/screen/widget/CatalogueCheckBoxButton.java (92%) rename src/main/java/com/{mrcrayfish => cleanroommc}/catalogue/client/screen/widget/CatalogueIconButton.java (93%) rename src/main/java/com/{mrcrayfish => cleanroommc}/catalogue/client/screen/widget/CatalogueListExtended.java (99%) rename src/main/java/com/{mrcrayfish => cleanroommc}/catalogue/client/screen/widget/CatalogueTextField.java (98%) rename src/main/java/com/{mrcrayfish/catalogue => cleanroommc/common}/CatalogueContainer.java (93%) diff --git a/src/main/java/com/mrcrayfish/catalogue/Constants.java b/src/main/java/com/cleanroommc/catalogue/Constants.java similarity index 88% rename from src/main/java/com/mrcrayfish/catalogue/Constants.java rename to src/main/java/com/cleanroommc/catalogue/Constants.java index cc788657e..329bf4abe 100644 --- a/src/main/java/com/mrcrayfish/catalogue/Constants.java +++ b/src/main/java/com/cleanroommc/catalogue/Constants.java @@ -1,4 +1,4 @@ -package com.mrcrayfish.catalogue; +package com.cleanroommc.catalogue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/mrcrayfish/catalogue/client/ClientHelper.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java similarity index 97% rename from src/main/java/com/mrcrayfish/catalogue/client/ClientHelper.java rename to src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java index bd3b3a74e..4f478dfdb 100644 --- a/src/main/java/com/mrcrayfish/catalogue/client/ClientHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java @@ -1,4 +1,4 @@ -package com.mrcrayfish.catalogue.client; +package com.cleanroommc.catalogue.client; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; diff --git a/src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java similarity index 99% rename from src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java rename to src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 901903bfd..4aceaf890 100644 --- a/src/main/java/com/mrcrayfish/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -1,7 +1,7 @@ -package com.mrcrayfish.catalogue.client.screen; +package com.cleanroommc.catalogue.client.screen; import com.google.common.collect.Lists; -import com.mrcrayfish.catalogue.Constants; +import com.cleanroommc.catalogue.Constants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiOptions; @@ -27,11 +27,11 @@ import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.client.IModGuiFactory; -import com.mrcrayfish.catalogue.client.ClientHelper; -import com.mrcrayfish.catalogue.client.screen.widget.CatalogueCheckBoxButton; -import com.mrcrayfish.catalogue.client.screen.widget.CatalogueIconButton; -import com.mrcrayfish.catalogue.client.screen.widget.CatalogueListExtended; -import com.mrcrayfish.catalogue.client.screen.widget.CatalogueTextField; +import com.cleanroommc.catalogue.client.ClientHelper; +import com.cleanroommc.catalogue.client.screen.widget.CatalogueCheckBoxButton; +import com.cleanroommc.catalogue.client.screen.widget.CatalogueIconButton; +import com.cleanroommc.catalogue.client.screen.widget.CatalogueListExtended; +import com.cleanroommc.catalogue.client.screen.widget.CatalogueTextField; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.ModMetadata; diff --git a/src/main/java/com/mrcrayfish/catalogue/client/screen/MinecraftContainer.java b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftContainer.java similarity index 95% rename from src/main/java/com/mrcrayfish/catalogue/client/screen/MinecraftContainer.java rename to src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftContainer.java index a76b2e007..9a014a648 100644 --- a/src/main/java/com/mrcrayfish/catalogue/client/screen/MinecraftContainer.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftContainer.java @@ -1,4 +1,4 @@ -package com.mrcrayfish.catalogue.client.screen; +package com.cleanroommc.catalogue.client.screen; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.common.DummyModContainer; diff --git a/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java similarity index 92% rename from src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueCheckBoxButton.java rename to src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java index 74693373b..17b36db3f 100644 --- a/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueCheckBoxButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java @@ -1,11 +1,11 @@ -package com.mrcrayfish.catalogue.client.screen.widget; +package com.cleanroommc.catalogue.client.screen.widget; -import com.mrcrayfish.catalogue.Constants; +import com.cleanroommc.catalogue.Constants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; -import com.mrcrayfish.catalogue.client.ClientHelper; +import com.cleanroommc.catalogue.client.ClientHelper; /** * Author: MrCrayfish diff --git a/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java similarity index 93% rename from src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueIconButton.java rename to src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index 27587353e..25aef38f5 100644 --- a/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -1,13 +1,13 @@ -package com.mrcrayfish.catalogue.client.screen.widget; +package com.cleanroommc.catalogue.client.screen.widget; -import com.mrcrayfish.catalogue.Constants; +import com.cleanroommc.catalogue.Constants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; -import com.mrcrayfish.catalogue.client.ClientHelper; +import com.cleanroommc.catalogue.client.ClientHelper; /** * Author: MrCrayfish diff --git a/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java similarity index 99% rename from src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueListExtended.java rename to src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index 3c6e3bb5a..2fbf78fce 100644 --- a/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -1,4 +1,4 @@ -package com.mrcrayfish.catalogue.client.screen.widget; +package com.cleanroommc.catalogue.client.screen.widget; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiListExtended; diff --git a/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueTextField.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java similarity index 98% rename from src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueTextField.java rename to src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java index 3afd85c42..218770f6e 100644 --- a/src/main/java/com/mrcrayfish/catalogue/client/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java @@ -1,4 +1,4 @@ -package com.mrcrayfish.catalogue.client.screen.widget; +package com.cleanroommc.catalogue.client.screen.widget; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; diff --git a/src/main/java/com/mrcrayfish/catalogue/CatalogueContainer.java b/src/main/java/com/cleanroommc/common/CatalogueContainer.java similarity index 93% rename from src/main/java/com/mrcrayfish/catalogue/CatalogueContainer.java rename to src/main/java/com/cleanroommc/common/CatalogueContainer.java index 05e7489af..632435617 100644 --- a/src/main/java/com/mrcrayfish/catalogue/CatalogueContainer.java +++ b/src/main/java/com/cleanroommc/common/CatalogueContainer.java @@ -1,5 +1,6 @@ -package com.mrcrayfish.catalogue; +package com.cleanroommc.common; +import com.cleanroommc.catalogue.Constants; import com.google.common.eventbus.EventBus; import net.minecraftforge.fml.common.DummyModContainer; import net.minecraftforge.fml.common.LoadController; diff --git a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java index e66fdd4b9..5885ae871 100644 --- a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java +++ b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java @@ -91,7 +91,7 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.common.util.CompoundDataFixer; -import com.mrcrayfish.catalogue.client.screen.CatalogueModListScreen; +import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; import net.minecraftforge.fml.client.registry.RenderingRegistry; import net.minecraftforge.fml.common.DummyModContainer; import net.minecraftforge.fml.common.DuplicateModsFoundException; diff --git a/src/main/java/net/minecraftforge/fml/client/GuiModList.java b/src/main/java/net/minecraftforge/fml/client/GuiModList.java index ac5198c95..e5577b471 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiModList.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiModList.java @@ -1,7 +1,7 @@ package net.minecraftforge.fml.client; import net.minecraft.client.gui.GuiScreen; -import com.mrcrayfish.catalogue.client.screen.CatalogueModListScreen; +import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; @Deprecated public class GuiModList extends CatalogueModListScreen { diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index 55a127cf4..47a03891d 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -40,7 +40,7 @@ import com.cleanroommc.common.CleanroomContainer; import com.cleanroommc.common.MixinContainer; import com.cleanroommc.common.ConfigAnytimeContainer; -import com.mrcrayfish.catalogue.CatalogueContainer; +import com.cleanroommc.common.CatalogueContainer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.common.capabilities.CapabilityManager; From e14e608d8e5fecc7e2b5f041098f16d08639873e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:57:33 +0800 Subject: [PATCH 29/91] Regen patches --- .../minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch index 1322307f8..fa6c661ea 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch @@ -21,7 +21,7 @@ import net.minecraft.world.WorldServerDemo; import net.minecraft.world.storage.ISaveFormat; import net.minecraft.world.storage.WorldInfo; -+import com.mrcrayfish.catalogue.client.screen.CatalogueModListScreen; ++import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import org.apache.commons.io.IOUtils; From 0b50e24bd98e97d51aa245c60156d36c17997f63 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:01:42 +0800 Subject: [PATCH 30/91] Minecraft Container --- .../client/screen/CatalogueModListScreen.java | 1 - .../client/screen/MinecraftContainer.java | 23 ------------------- .../fml/common/MinecraftDummyContainer.java | 15 +++++++++--- 3 files changed, 12 insertions(+), 27 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftContainer.java diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 4aceaf890..183281e86 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -82,7 +82,6 @@ public CatalogueModListScreen(GuiScreen parent) { this.parentScreen = parent; if (!loaded) { Loader.instance().getActiveModList().forEach(data -> CACHED_MODS.put(data.getModId(), data)); - CACHED_MODS.put("minecraft", new MinecraftContainer()); // Override minecraft BANNER_CACHE.put("minecraft", new ImageInfo(MINECRAFT_LOGO, new Dimension(1024, 256))); loaded = true; } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftContainer.java b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftContainer.java deleted file mode 100644 index 9a014a648..000000000 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftContainer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.cleanroommc.catalogue.client.screen; - -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.common.DummyModContainer; -import net.minecraftforge.fml.common.ModMetadata; - -import java.util.List; - -public class MinecraftContainer extends DummyModContainer { - public MinecraftContainer() { - super(new ModMetadata()); - ModMetadata meta = this.getMetadata(); - meta.modId = "minecraft"; - meta.name = "Minecraft"; - // Description provided by minecraft.wiki (CC BY-NC-SA 3.0) - meta.description = "Minecraft is a 3D sandbox adventure game developed by Mojang Studios where players can interact with a fully customizable three-dimensional world made of blocks and entities. Its diverse gameplay options allow players to choose the way they play, creating countless possibilities."; - meta.version = ForgeVersion.mcVersion; - meta.authorList = List.of("Mojang AB"); - meta.url = "https://www.minecraft.net"; - meta.issueTrackerUrl = "https://bugs.mojang.com/projects/MC/issues"; - meta.license = "All Rights Reserved"; - } -} diff --git a/src/main/java/net/minecraftforge/fml/common/MinecraftDummyContainer.java b/src/main/java/net/minecraftforge/fml/common/MinecraftDummyContainer.java index 78d287bf6..1318d3a1f 100644 --- a/src/main/java/net/minecraftforge/fml/common/MinecraftDummyContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/MinecraftDummyContainer.java @@ -21,6 +21,7 @@ import java.io.File; import java.security.cert.Certificate; +import java.util.List; import com.google.common.eventbus.EventBus; import net.minecraftforge.fml.common.versioning.VersionParser; @@ -37,9 +38,17 @@ public class MinecraftDummyContainer extends DummyModContainer public MinecraftDummyContainer(String actualMCVersion) { super(new ModMetadata()); - getMetadata().modId = "minecraft"; - getMetadata().name = "Minecraft"; - getMetadata().version = actualMCVersion; + ModMetadata meta = this.getMetadata(); + meta.modId = "minecraft"; + meta.name = "Minecraft"; + meta.version = actualMCVersion; + // Description provided by minecraft.wiki (CC BY-NC-SA 3.0) + meta.description = "Minecraft is a 3D sandbox adventure game developed by Mojang Studios where players can interact with a fully customizable three-dimensional world made of blocks and entities. Its diverse gameplay options allow players to choose the way they play, creating countless possibilities."; + meta.authorList = List.of("Mojang AB"); + meta.url = "https://www.minecraft.net"; + meta.issueTrackerUrl = "https://bugs.mojang.com/projects/MC/issues"; + meta.license = "All Rights Reserved"; + staticRange = VersionParser.parseRange("["+actualMCVersion+"]"); } From 19780b8eff792dc8fb68ce719838b4981940849e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:08:05 +0800 Subject: [PATCH 31/91] Remove banner.png --- .../cleanroommc/common/CatalogueContainer.java | 2 +- src/main/resources/assets/catalogue/banner.png | Bin 2635 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/main/resources/assets/catalogue/banner.png diff --git a/src/main/java/com/cleanroommc/common/CatalogueContainer.java b/src/main/java/com/cleanroommc/common/CatalogueContainer.java index 632435617..24f892b5c 100644 --- a/src/main/java/com/cleanroommc/common/CatalogueContainer.java +++ b/src/main/java/com/cleanroommc/common/CatalogueContainer.java @@ -19,7 +19,7 @@ public CatalogueContainer() { meta.authorList = Arrays.asList("MrCrayfish", "RuiXuqi"); meta.credits = "Hatsondogs for creating icons"; meta.license = "MIT"; - meta.logoFile = "/assets/catalogue/banner.png"; + meta.logoFile = "/assets/catalogue/icon.png"; meta.logoBlur = false; meta.iconFile = "/assets/catalogue/icon.png"; meta.iconBlur = false; diff --git a/src/main/resources/assets/catalogue/banner.png b/src/main/resources/assets/catalogue/banner.png deleted file mode 100644 index ab238d3ac1e06762c7d4ff40b1c88453dded6894..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2635 zcmcImZHQD=7`~%xyDG|xCMMBduJrAh`#JXu$K7>i+Z|}uWgYV4hji{a=g!`onLFKk zXJ^(579lVUtRGpJf`(xbX=Owpq)8BlmWr@`ELe2ze7UZ>+afe@&wK8< z?|I(m{W@n}80ha^vS{@phGCYZ`jUfG9txkfyXd&*d=^l#u-LcNXPCvy!zaS*Kd_2n z+Bhe(CD@XF+(d3Z3bAXEXeD2y+6=Rd;2MJh0*Km zIej}lvT0|32JPH|49s<|XV+Cs3XmrOWGnex!8a>$u8M2YdAQ7TY*i)L5$C!?gY1^{ z0Gn_IBu8Kze1e5VEWGb2U94-(%kd5H83 zlR~hjVXQdh2ChHk&cLnt`4)y4H4PR^UalG*jChhGd5Y~*U9rlqXeK-Y1J}#AZmyB2 zfrdpkkq8rGun%o^3fL|C4~L}cdq@%nB+f~KBmzMJf|L<8QxwdY_9z`?p{AOqrGQ}o zCn<{}5cN=55=~jWMcKuUJu(;7blOZ6`~VgZNhRYPB^7lXY}yzbM6q>%F%*EIDI(Af zfesQTgh(p1WVOnR?oYa?6b2*QU#nc~B8t%rsA)qL+Ss-zU0noNQY>HyN(>-kX|Wh0 zatvt=w2huan+N75qlT<7MKmlShL9}-NGLT)QB+{)$O4oMf;JIk0~#EA({94exnA1E zG`jNS^;9A;;JLPwqaXai-VJQ3C!valssbr0ax6xs?Rt4gYt6~SEa8iVY*_h}26ea3 z@oAbz>bCFs#>ecRiw?PHk8 z`BbtyQ~B=K@84T^rfbFMmF4$#9XP%HlVu;A2o{{3x9gi@m+#xRPdx@--`;-rrrwru zW#jJNkB`5y=E`rsjyvoc5&g$?9s2@`1`4q+d9khf8}I-0+uEsEb_jIUX5qp= z_n+Q=HL~ZYx4xbj-FxxP3$4$dymV;_a&=pMcc*ndvX@+2@Y(pVc5Up`QGfm}W*OVo z9=Tl{S$eMZ#nnq&w|2ZcF$?0UbFH5)X|k)EX^N{IhbBhvkvl@>&Ba^HU4C_9^rOfO z9NJ#GF<gi7&+wkPBe*rSRIO6~S From c91e378a4c013975a099a9ce1a3996f37441f2be Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:21:30 +0800 Subject: [PATCH 32/91] CatalogueConstants --- ...Constants.java => CatalogueConstants.java} | 2 +- .../client/screen/CatalogueModListScreen.java | 22 +++++++++---------- .../widget/CatalogueCheckBoxButton.java | 4 ++-- .../screen/widget/CatalogueIconButton.java | 4 ++-- .../common/CatalogueContainer.java | 6 ++--- 5 files changed, 19 insertions(+), 19 deletions(-) rename src/main/java/com/cleanroommc/catalogue/{Constants.java => CatalogueConstants.java} (89%) diff --git a/src/main/java/com/cleanroommc/catalogue/Constants.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConstants.java similarity index 89% rename from src/main/java/com/cleanroommc/catalogue/Constants.java rename to src/main/java/com/cleanroommc/catalogue/CatalogueConstants.java index 329bf4abe..b1bfd13f0 100644 --- a/src/main/java/com/cleanroommc/catalogue/Constants.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConstants.java @@ -3,7 +3,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Constants { +public class CatalogueConstants { public static final String MOD_ID = "catalogue"; public static final String MOD_NAME = "Catalogue"; public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 183281e86..b5da9fc5d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -1,7 +1,7 @@ package com.cleanroommc.catalogue.client.screen; import com.google.common.collect.Lists; -import com.cleanroommc.catalogue.Constants; +import com.cleanroommc.catalogue.CatalogueConstants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiOptions; @@ -48,10 +48,10 @@ import java.util.stream.Collectors; public class CatalogueModListScreen extends GuiScreen { - private static final ResourceLocation MISSING_BANNER = new ResourceLocation(Constants.MOD_ID, "textures/gui/missing_banner.png"); - private static final ResourceLocation MISSING_BACKGROUND = new ResourceLocation(Constants.MOD_ID, "textures/gui/missing_background.png"); + private static final ResourceLocation MISSING_BANNER = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/missing_banner.png"); + private static final ResourceLocation MISSING_BACKGROUND = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/missing_background.png"); private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); - private static final ResourceLocation MINECRAFT_LOGO = new ResourceLocation(Constants.MOD_ID, "textures/gui/minecraft.png"); + private static final ResourceLocation MINECRAFT_LOGO = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/minecraft.png"); private static final ImageInfo MISSING_BANNER_INFO = new ImageInfo(MISSING_BANNER, new Dimension(120, 120)); private static final Map BANNER_CACHE = new HashMap<>(); private static final Map IMAGE_ICON_CACHE = new HashMap<>(); @@ -145,7 +145,7 @@ public void actionPerformed(GuiButton button) { try { Desktop.getDesktop().open(Loader.instance().getConfigDir().getParentFile().toPath().resolve("mods").toFile()); } catch (Exception e) { - Constants.LOG.error("Problem opening mods folder", e); + CatalogueConstants.LOG.error("Problem opening mods folder", e); } break; case 3: @@ -155,7 +155,7 @@ public void actionPerformed(GuiButton button) { GuiScreen newScreen = guiFactory.createConfigGui(this); this.mc.displayGuiScreen(newScreen); } catch (Exception e) { - Constants.LOG.error("There was a critical issue trying to build the config GUI for {}", this.selectedModData.getModId(), e); + CatalogueConstants.LOG.error("There was a critical issue trying to build the config GUI for {}", this.selectedModData.getModId(), e); } } else { this.mc.displayGuiScreen(new GuiOptions(this, this.mc.gameSettings)); @@ -182,9 +182,9 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawModInfo(mouseX, mouseY, partialTicks); super.drawScreen(mouseX, mouseY, partialTicks); - Optional optional = Optional.ofNullable(CACHED_MODS.get(Constants.MOD_ID)); + Optional optional = Optional.ofNullable(CACHED_MODS.get(CatalogueConstants.MOD_ID)); optional.ifPresent(this::loadAndCacheLogo); - ImageInfo imageInfo = BANNER_CACHE.get(Constants.MOD_ID); + ImageInfo imageInfo = BANNER_CACHE.get(CatalogueConstants.MOD_ID); if (imageInfo != null) { Dimension size = imageInfo.size(); this.mc.getTextureManager().bindTexture(imageInfo.resource()); @@ -388,7 +388,7 @@ private void drawIcon(int top, int left) { RenderHelper.disableStandardItemLighting(); } catch (Exception e) { // Attempt to catch exceptions when rendering item. Sometime level instance isn't checked for null - Constants.LOG.debug("Failed to draw icon for mod '{}'", this.data.getModId(), e); + CatalogueConstants.LOG.debug("Failed to draw icon for mod '{}'", this.data.getModId(), e); ITEM_ICON_CACHE.put(this.data.getModId(), new ItemStack(Blocks.GRASS)); this.icon = new ItemStack(Blocks.GRASS); } @@ -434,7 +434,7 @@ private ItemStack getItemIcon() { return itemStack; } } catch (Exception e) { - Constants.LOG.debug("Failed to get customized item icon for mod '{}'", this.data.getModId(), e); + CatalogueConstants.LOG.debug("Failed to get customized item icon for mod '{}'", this.data.getModId(), e); } } } @@ -452,7 +452,7 @@ private ItemStack getItemIcon() { item = tabItem; } } catch (Exception e) { - Constants.LOG.debug("Failed to get creative tab icon for mod '{}'", this.data.getModId(), e); + CatalogueConstants.LOG.debug("Failed to get creative tab icon for mod '{}'", this.data.getModId(), e); } } ITEM_ICON_CACHE.put(this.data.getModId(), item); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java index 17b36db3f..e32098d35 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java @@ -1,6 +1,6 @@ package com.cleanroommc.catalogue.client.screen.widget; -import com.cleanroommc.catalogue.Constants; +import com.cleanroommc.catalogue.CatalogueConstants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; @@ -11,7 +11,7 @@ * Author: MrCrayfish */ public class CatalogueCheckBoxButton extends GuiButton { - private static final ResourceLocation TEXTURE = new ResourceLocation(Constants.MOD_ID, "textures/gui/checkbox.png"); + private static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/checkbox.png"); private boolean selected; public CatalogueCheckBoxButton(int id, int x, int y, boolean selectedDefault) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index 25aef38f5..09d7cdf93 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -1,6 +1,6 @@ package com.cleanroommc.catalogue.client.screen.widget; -import com.cleanroommc.catalogue.Constants; +import com.cleanroommc.catalogue.CatalogueConstants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; @@ -13,7 +13,7 @@ * Author: MrCrayfish */ public class CatalogueIconButton extends GuiButton { - private static final ResourceLocation TEXTURE = new ResourceLocation(Constants.MOD_ID, "textures/gui/icons.png"); + private static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/icons.png"); private final String label; private final int u, v; diff --git a/src/main/java/com/cleanroommc/common/CatalogueContainer.java b/src/main/java/com/cleanroommc/common/CatalogueContainer.java index 24f892b5c..b6e4f3399 100644 --- a/src/main/java/com/cleanroommc/common/CatalogueContainer.java +++ b/src/main/java/com/cleanroommc/common/CatalogueContainer.java @@ -1,6 +1,6 @@ package com.cleanroommc.common; -import com.cleanroommc.catalogue.Constants; +import com.cleanroommc.catalogue.CatalogueConstants; import com.google.common.eventbus.EventBus; import net.minecraftforge.fml.common.DummyModContainer; import net.minecraftforge.fml.common.LoadController; @@ -12,8 +12,8 @@ public class CatalogueContainer extends DummyModContainer { public CatalogueContainer() { super(new ModMetadata()); ModMetadata meta = this.getMetadata(); - meta.modId = Constants.MOD_ID; - meta.name = Constants.MOD_NAME; + meta.modId = CatalogueConstants.MOD_ID; + meta.name = CatalogueConstants.MOD_NAME; meta.description = "Updates Forge's mod list with a new and improved design"; meta.version = "1.0.0"; meta.authorList = Arrays.asList("MrCrayfish", "RuiXuqi"); From b964282af786a98248eb8029c40965676ed606d8 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:32:41 +0800 Subject: [PATCH 33/91] Call directly --- .../cleanroommc/catalogue/client/ClientHelper.java | 9 --------- .../client/screen/CatalogueModListScreen.java | 12 ++++++------ .../screen/widget/CatalogueCheckBoxButton.java | 2 +- .../client/screen/widget/CatalogueIconButton.java | 2 +- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java index 4f478dfdb..3a73a71b3 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java @@ -33,13 +33,4 @@ public static void scissor(int screenX, int screenY, int boxWidth, int boxHeight public static boolean isMouseWithin(int x, int y, int width, int height, int mouseX, int mouseY) { return mouseX >= x && mouseX < x + width && mouseY >= y && mouseY < y + height; } - - public static void blit(int x, int y, int width, int height, float uOffset, float vOffset, int uWidth, int vHeight, int textureWidth, int textureHeight) { - Gui.drawScaledCustomSizeModalRect(x, y, uOffset, vOffset, uWidth, vHeight, width, height, textureWidth, textureHeight); - } - - public static void blit(int x, int y, float uOffset, float vOffset, int width, int height, int textureWidth, int textureHeight) { - Gui.drawModalRectWithCustomSizedTexture(x, y, uOffset, vOffset, width, height, textureWidth, textureHeight); - } - } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index b5da9fc5d..f7a31e80d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -188,7 +188,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { if (imageInfo != null) { Dimension size = imageInfo.size(); this.mc.getTextureManager().bindTexture(imageInfo.resource()); - ClientHelper.blit(10, 9, 10, 10, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + drawScaledCustomSizeModalRect(10, 9, 0.0F, 0.0F, size.width, size.height, 10, 10, size.width, size.height); } if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { @@ -363,7 +363,7 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); int vOffset = update.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; - ClientHelper.blit(left + rowWidth - 8 - 10, top + 6, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); + drawModalRectWithCustomSizedTexture(left + rowWidth - 8 - 10, top + 6, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); } } @@ -376,7 +376,7 @@ private void drawIcon(int top, int left) { GlStateManager.enableBlend(); Dimension size = iconInfo.size(); mc.getTextureManager().bindTexture(iconInfo.resource()); - ClientHelper.blit(left + 4, top + 3, 16, 16, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + drawScaledCustomSizeModalRect(left + 4, top + 3, 0.0F, 0.0F, size.width, size.height, 16, 16, size.width, size.height); GlStateManager.disableBlend(); return; } @@ -674,7 +674,7 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { GlStateManager.enableBlend(); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); this.mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); - ClientHelper.blit(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); + drawModalRectWithCustomSizedTexture(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); GlStateManager.disableBlend(); this.modList.drawScreen(mouseX, mouseY, partialTicks); @@ -743,7 +743,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); this.mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); int vOffset = update.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; - ClientHelper.blit(contentLeft + versionWidth + 5, 92, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); + drawModalRectWithCustomSizedTexture(contentLeft + versionWidth + 5, 92, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { switch (update.status) { case BETA: @@ -1022,7 +1022,7 @@ private void drawBanner(int contentWidth, int x, int y, int maxWidth, int maxHei GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); this.mc.getTextureManager().bindTexture(bannerInfo.resource()); - ClientHelper.blit(x, y, width, height, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + drawScaledCustomSizeModalRect(x, y, 0.0F, 0.0F, size.width, size.height, width, height, size.width, size.height); GlStateManager.disableBlend(); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java index e32098d35..d6984e5bf 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java @@ -39,7 +39,7 @@ public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partia GlStateManager.enableBlend(); GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - ClientHelper.blit(this.x, this.y, this.hovered ? 14 : 0, this.selected() ? 14 : 0, 14, 14, 64, 64); + drawModalRectWithCustomSizedTexture(this.x, this.y, this.hovered ? 14 : 0, this.selected() ? 14 : 0, 14, 14, 64, 64); this.mouseDragged(minecraft, mouseX, mouseY); } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index 09d7cdf93..70a14d6f3 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -43,7 +43,7 @@ public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partia int iconY = this.y + 5; float brightness = this.enabled ? 1.0F : 0.5F; GlStateManager.color(brightness, brightness, brightness, 1.0F); - ClientHelper.blit(iconX, iconY, this.u, this.v, 10, 10, 64, 64); + drawModalRectWithCustomSizedTexture(iconX, iconY, this.u, this.v, 10, 10, 64, 64); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); int textColor = this.getFGColor() | MathHelper.ceil(255.0F) << 24; drawString(fontrenderer, this.label, iconX + 14, iconY + 1, textColor); From d8fd7c9486d6a0eddfaf00c0a8af87c28e42d33b Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:46:22 +0800 Subject: [PATCH 34/91] Configurable list of lib mods --- .../catalogue/CatalogueConfig.java | 36 +++++++++++++++++++ .../client/screen/CatalogueModListScreen.java | 30 ++++++++++++++-- .../common/CatalogueContainer.java | 5 +++ .../common/ConfigAnytimeContainer.java | 2 -- .../assets/catalogue/lang/en_us.lang | 6 +++- .../assets/catalogue/lang/zh_cn.lang | 6 +++- 6 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java new file mode 100644 index 000000000..a20b1e9ee --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -0,0 +1,36 @@ +package com.cleanroommc.catalogue; + +import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; +import net.minecraftforge.common.config.Config; +import net.minecraftforge.common.config.ConfigManager; +import net.minecraftforge.fml.client.event.ConfigChangedEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +@Config(modid = CatalogueConstants.MOD_ID) +@Mod.EventBusSubscriber(modid = CatalogueConstants.MOD_ID) +public class CatalogueConfig { + @Config.RequiresMcRestart + @Config.Comment({ + "The list of library mods' mod ids.", + "They will have grey names in the mod list." + }) + @Config.LangKey("catalogue.config.library_list") + public static String[] libraryList = new String[] { + "minecraft", + "forge", + "fml", + "cleanroom", + "configanytime", + "mixinbooter", + "fugue", + "scalar" + }; + + @SubscribeEvent + public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) { + if (event.getModID().equals(CatalogueConstants.MOD_ID)) { + ConfigManager.sync(CatalogueConstants.MOD_ID, Config.Type.INSTANCE); + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index f7a31e80d..61a60691e 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -1,5 +1,7 @@ package com.cleanroommc.catalogue.client.screen; +import com.cleanroommc.catalogue.CatalogueConfig; +import com.google.common.base.Suppliers; import com.google.common.collect.Lists; import com.cleanroommc.catalogue.CatalogueConstants; import net.minecraft.client.Minecraft; @@ -36,6 +38,7 @@ import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.ModMetadata; import net.minecraftforge.fml.common.registry.ForgeRegistries; +import org.apache.commons.lang3.tuple.Pair; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.GL11; @@ -45,6 +48,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; public class CatalogueModListScreen extends GuiScreen { @@ -57,6 +61,12 @@ public class CatalogueModListScreen extends GuiScreen { private static final Map IMAGE_ICON_CACHE = new HashMap<>(); private static final Map ITEM_ICON_CACHE = new HashMap<>(); private static final Map CACHED_MODS = new HashMap<>(); + private static final List LIB_MODS = Arrays.asList(CatalogueConfig.libraryList); + private static final Supplier> COUNTS = Suppliers.memoize(() -> { + int[] counts = new int[2]; + CACHED_MODS.forEach((modId, data) -> counts[LIB_MODS.contains(data.getModId()) ? 1 : 0]++); + return Pair.of(counts[0], counts[1]); + }); private static ResourceLocation cachedBackground; private static boolean loaded = false; private static String lastSearch = ""; @@ -197,7 +207,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { } if (this.modFolderButton.isMouseOver()) { - this.setActiveTooltip(I18n.format("fml.button.open.mods.folder")); + this.setActiveTooltip(I18n.format("catalogue.gui.open_mods_folder")); } if (this.activeTooltip != null) { @@ -469,7 +479,7 @@ private String getFormattedModName() { if (CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > width) { name = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(name, width - 10) + "..."; } - if (this.data.getModId().equals("forge") || this.data.getModId().equals("minecraft") || this.data.getModId().equals("cleanroom")) { + if (LIB_MODS.contains(this.data.getModId())) { return TextFormatting.DARK_GRAY + name; } return name; @@ -687,6 +697,17 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { int titleLeft = this.modList.left + (this.modList.width - titleWidth) / 2; drawString(this.fontRenderer, title, titleLeft, 10, 0xFFFFFF); + int countLabelWidth = this.fontRenderer.getStringWidth(countLabel); + if (ClientHelper.isMouseWithin(titleLeft + titleWidth - countLabelWidth, 10, countLabelWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { + Pair counts = COUNTS.get(); + List lines = List.of( + I18n.format("catalogue.gui.mod_count", counts.getLeft()), + I18n.format("catalogue.gui.library_count", counts.getRight()) + ); + this.setActiveTooltip(lines); + this.tooltipYOffset = 10; + } + if (ClientHelper.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { this.setActiveTooltip(I18n.format("catalogue.gui.filter_updates")); this.tooltipYOffset = 10; @@ -1051,6 +1072,11 @@ private void setActiveTooltip(String content) { this.tooltipYOffset = 0; } + private void setActiveTooltip(List activeTooltip) { + this.activeTooltip = activeTooltip; + this.tooltipYOffset = 0; + } + private void setSelectedModData(ModContainer data) { this.selectedModData = data; this.loadAndCacheLogo(data); diff --git a/src/main/java/com/cleanroommc/common/CatalogueContainer.java b/src/main/java/com/cleanroommc/common/CatalogueContainer.java index b6e4f3399..77e7b333b 100644 --- a/src/main/java/com/cleanroommc/common/CatalogueContainer.java +++ b/src/main/java/com/cleanroommc/common/CatalogueContainer.java @@ -1,7 +1,10 @@ package com.cleanroommc.common; +import com.cleanroommc.catalogue.CatalogueConfig; import com.cleanroommc.catalogue.CatalogueConstants; import com.google.common.eventbus.EventBus; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.fml.common.DummyModContainer; import net.minecraftforge.fml.common.LoadController; import net.minecraftforge.fml.common.ModMetadata; @@ -24,6 +27,8 @@ public CatalogueContainer() { meta.iconFile = "/assets/catalogue/icon.png"; meta.iconBlur = false; meta.backgroundFile = "/assets/catalogue/background.png"; + ConfigManager.register(CatalogueConfig.class); + MinecraftForge.EVENT_BUS.register(CatalogueConfig.class); } @Override diff --git a/src/main/java/com/cleanroommc/common/ConfigAnytimeContainer.java b/src/main/java/com/cleanroommc/common/ConfigAnytimeContainer.java index fc182736e..60cf6952a 100644 --- a/src/main/java/com/cleanroommc/common/ConfigAnytimeContainer.java +++ b/src/main/java/com/cleanroommc/common/ConfigAnytimeContainer.java @@ -6,8 +6,6 @@ import net.minecraftforge.fml.common.LoadController; import net.minecraftforge.fml.common.ModMetadata; -import java.util.Arrays; - public class ConfigAnytimeContainer extends DummyModContainer { public ConfigAnytimeContainer() { super(new ModMetadata()); diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index 55ecde2a0..0d6a2efeb 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -18,4 +18,8 @@ catalogue.gui.ahead=This version is ahead of the latest version found (%s) catalogue.gui.update_available=Update (%s) Available: %s catalogue.gui.update_available_no_page=Update (%s) Available catalogue.gui.beta_update_available=Beta Update (%s) Available: %s -catalogue.gui.beta_update_available_no_page=Beta Update (%s) Available \ No newline at end of file +catalogue.gui.beta_update_available_no_page=Beta Update (%s) Available +catalogue.gui.mod_count=%s Mods +catalogue.gui.library_count=%s Libraries + +catalogue.config.library_list=Library List diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index 22decae29..7990c432c 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -18,4 +18,8 @@ catalogue.gui.ahead=此版本高于搜索到的最新版(%s) catalogue.gui.update_available=更新(%s)可用:%s catalogue.gui.update_available_no_page=更新(%s)可用 catalogue.gui.beta_update_available=Beta 更新(%s)可用:%s -catalogue.gui.beta_update_available_no_page=Beta 更新(%s)可用 \ No newline at end of file +catalogue.gui.beta_update_available_no_page=Beta 更新(%s)可用 +catalogue.gui.mod_count=%s个模组 +catalogue.gui.library_count=%s个库 + +catalogue.config.library_list=库列表 From 0683241d6f920a164b8731c3b3a65e74e208db9e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:54:53 +0800 Subject: [PATCH 35/91] Update CatalogueConfig.java --- src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index a20b1e9ee..99fabb1d2 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -19,7 +19,8 @@ public class CatalogueConfig { public static String[] libraryList = new String[] { "minecraft", "forge", - "fml", + "FML", + "mcp", "cleanroom", "configanytime", "mixinbooter", From 1a330b0ae57f2551ef56065aaaffa81df0d43d61 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:49:46 +0800 Subject: [PATCH 36/91] Make the structure clearer --- .../client/screen/CatalogueModListScreen.java | 473 +++++++++--------- .../screen/widget/CatalogueListExtended.java | 2 - 2 files changed, 235 insertions(+), 240 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 61a60691e..5b12890d6 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -217,17 +217,62 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { } @Override - protected void keyTyped(char typedChar, int key) throws IOException { - if (this.searchTextField.textboxKeyTyped(typedChar, key)) { - String s = this.searchTextField.getText(); - this.updateSearchField(s); - this.modList.filterAndUpdateList(s); - this.updateSelectedModList(); - lastSearch = s; + public void handleMouseInput() throws IOException { + super.handleMouseInput(); + this.modList.handleMouseInput(); + this.descriptionList.handleMouseInput(); + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int button) throws IOException { + // Catalogue button + if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY) && button == 0) { + this.openLink("https://www.curseforge.com/minecraft/mc-mods/catalogue"); return; } - super.keyTyped(typedChar, key); + // Version check button + if (this.selectedModData != null) { + int contentLeft = this.modList.right + 12 + 10; + String version = I18n.format("catalogue.gui.version", this.selectedModData.getDisplayVersion()); + int versionWidth = this.fontRenderer.getStringWidth(version); + if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { + ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); + if (shouldUpdate(update) && update.homepage != null) { + this.openLink(update.homepage); + } + } + } + + // Search Text Field + if (ClientHelper.isMouseWithin(this.searchTextField.x, this.searchTextField.y, this.searchTextField.width, this.searchTextField.height, mouseX, mouseY)) { + // Right click to empty + if (button == 1) { + this.searchTextField.setText(""); + this.updateSearchField(""); + this.modList.filterAndUpdateList(""); + lastSearch = ""; + return; + } + // Left click to apply suggestions + if (button == 0) { + String text = this.searchTextField.getText(); + long currentTine = Minecraft.getSystemTime(); + if (!text.isEmpty() && currentTine - this.lastClickTime < 250L && !this.searchTextField.getIsTextTruncated()) { + text += this.searchTextField.getSuggestion(); + this.searchTextField.setText(text); + this.updateSearchField(text); + this.modList.filterAndUpdateList(text); + lastSearch = text; + this.lastClickTime = currentTine; + return; + } + this.lastClickTime = currentTine; + } + } + this.searchTextField.mouseClicked(mouseX, mouseY, button); + + super.mouseClicked(mouseX, mouseY, button); } @Override @@ -242,6 +287,66 @@ public void handleKeyboardInput() throws IOException { super.handleKeyboardInput(); } + @Override + protected void keyTyped(char typedChar, int key) throws IOException { + if (this.searchTextField.textboxKeyTyped(typedChar, key)) { + String s = this.searchTextField.getText(); + this.updateSearchField(s); + this.modList.filterAndUpdateList(s); + this.updateSelectedModList(); + lastSearch = s; + return; + } + super.keyTyped(typedChar, key); + } + + @Override + public void updateScreen() { + super.updateScreen(); + this.searchTextField.updateCursorCounter(); + } + + /** + * Draws everything considered left of the screen; title, search bar and mod list. + * + * @param mouseX the current mouse x position + * @param mouseY the current mouse y position + * @param partialTicks the partial ticks + */ + private void drawModList(int mouseX, int mouseY, float partialTicks) { + GlStateManager.enableBlend(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + this.mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); + drawModalRectWithCustomSizedTexture(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); + GlStateManager.disableBlend(); + + this.modList.drawScreen(mouseX, mouseY, partialTicks); + this.searchTextField.drawTextBox(); + + String modsLabel = TextFormatting.BOLD + I18n.format("catalogue.gui.title"); + String countLabel = TextFormatting.GRAY + "(" + CACHED_MODS.size() + ")"; + String title = modsLabel + " " + countLabel; + int titleWidth = this.fontRenderer.getStringWidth(title); + int titleLeft = this.modList.left + (this.modList.width - titleWidth) / 2; + drawString(this.fontRenderer, title, titleLeft, 10, 0xFFFFFF); + + int countLabelWidth = this.fontRenderer.getStringWidth(countLabel); + if (ClientHelper.isMouseWithin(titleLeft + titleWidth - countLabelWidth, 10, countLabelWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { + Pair counts = COUNTS.get(); + List lines = List.of( + I18n.format("catalogue.gui.mod_count", counts.getLeft()), + I18n.format("catalogue.gui.library_count", counts.getRight()) + ); + this.setActiveTooltip(lines); + this.tooltipYOffset = 10; + } + + if (ClientHelper.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { + this.setActiveTooltip(I18n.format("catalogue.gui.filter_updates")); + this.tooltipYOffset = 10; + } + } + private class ModList extends CatalogueListExtended { private List entries = Lists.newArrayList(); private int selectedIndex = -1; @@ -335,7 +440,7 @@ protected int getSize() { @Override protected boolean isSelected(int slotIndex) { - return slotIndex == selectedIndex; + return this.selectedIndex == slotIndex; } @Override @@ -404,12 +509,6 @@ private void drawIcon(int top, int left) { } } - @Override - public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { - this.list.selectMod(slotIndex); - return true; - } - private ItemStack getItemIcon() { if (ITEM_ICON_CACHE.containsKey(this.data.getModId())) { return ITEM_ICON_CACHE.get(this.data.getModId()); @@ -485,232 +584,17 @@ private String getFormattedModName() { return name; } - @Override - public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { - } - - @Override - public void updatePosition(int slotIndex, int x, int y, float partialTicks) { - } - } - - private class StringList extends CatalogueListExtended { - private List entries = Lists.newArrayList(); - - public StringList(int width, int height, int left, int top) { - super(CatalogueModListScreen.this.mc, width, height, top, top + height, 10); - this.setSlotXBoundsFromLeft(left + 8); - this.visible = false; - } - - public void setTextFromInfo(ModContainer data) { - this.entries.clear(); - this.visible = true; - if (data.getMetadata().description.trim().isBlank()) { - this.visible = false; - return; - } - String description = data.getMetadata().description.trim(); - List lines = CatalogueModListScreen.this.fontRenderer.listFormattedStringToWidth(description, this.getListWidth()); - - for (String line : lines) { - String cleanLine = line.replace("\n", "").replace("\r", "").trim(); - this.entries.add(new StringEntry(cleanLine)); - } - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - ClientHelper.scissor(this.left, this.top, this.width, this.bottom - this.top); - super.drawScreen(mouseX, mouseY, partialTicks); - GL11.glDisable(GL11.GL_SCISSOR_TEST); - } - - @Override - protected void drawContainerBackground(@Nullable Tessellator tessellator) { - int x = this.left; - int y = this.top; - int width = this.width; - int height = this.height; - drawRect(x, y + 1, x + 1, y + height - 1, 0x77000000); - drawRect(x + 1, y, x + width - 1, y + height, 0x77000000); - drawRect(x + width - 1, y + 1, x + width, y + height - 1, 0x77000000); - } - - @Override - protected int getScrollBarX() { - return this.left + this.width - 7; - } - - @Override - public int getListLeft() { - return this.left + 8; - } - - @Override - public int getListWidth() { - return this.width - 16; - } - - @Override - protected int getRowTop(int $$0) { - return super.getRowTop($$0) + 4; - } - - @Override - public int getMaxScroll() { - return Math.max(0, this.getContentHeight() - (this.height - 12)); - } - - @Override - protected int getSize() { - return this.entries.size(); - } - - @Override - public IGuiListEntry getListEntry(int index) { - return this.entries.get(index); - } - - @Override - protected void drawTopAndBottom(Tessellator tessellator) { - } - - @Override - protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { - } - } - - private class StringEntry implements CatalogueListExtended.IGuiListEntry { - private String line; - - public StringEntry(String line) { - this.line = line; - } - - @Override - public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { - drawString(CatalogueModListScreen.this.fontRenderer, this.line, left, top, 0xFFFFFF); - } - @Override public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { return true; } - @Override - public void updatePosition(int slotIndex, int x, int y, float partialTicks) { - } - @Override public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { } - } - - - @Override - public void handleMouseInput() throws IOException { - super.handleMouseInput(); - this.modList.handleMouseInput(); - this.descriptionList.handleMouseInput(); - } - - @Override - protected void mouseClicked(int mouseX, int mouseY, int button) throws IOException { - // Catalogue button - if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY) && button == 0) { - this.openLink("https://www.curseforge.com/minecraft/mc-mods/catalogue"); - return; - } - - // Version check button - if (this.selectedModData != null) { - int contentLeft = this.modList.right + 12 + 10; - String version = I18n.format("catalogue.gui.version", this.selectedModData.getDisplayVersion()); - int versionWidth = this.fontRenderer.getStringWidth(version); - if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { - ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); - if (shouldUpdate(update) && update.homepage != null) { - this.openLink(update.homepage); - } - } - } - - // Search Text Field - if (ClientHelper.isMouseWithin(this.searchTextField.x, this.searchTextField.y, this.searchTextField.width, this.searchTextField.height, mouseX, mouseY)) { - // Right click to empty - if (button == 1) { - this.searchTextField.setText(""); - this.updateSearchField(""); - this.modList.filterAndUpdateList(""); - lastSearch = ""; - return; - } - // Left click to apply suggestions - if (button == 0) { - String text = this.searchTextField.getText(); - long currentTine = Minecraft.getSystemTime(); - if (!text.isEmpty() && currentTine - this.lastClickTime < 250L && !this.searchTextField.getIsTextTruncated()) { - text += this.searchTextField.getSuggestion(); - this.searchTextField.setText(text); - this.updateSearchField(text); - this.modList.filterAndUpdateList(text); - lastSearch = text; - this.lastClickTime = currentTine; - return; - } - this.lastClickTime = currentTine; - } - } - this.searchTextField.mouseClicked(mouseX, mouseY, button); - - super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public void updateScreen() { - super.updateScreen(); - this.searchTextField.updateCursorCounter(); - } - - /** - * Draws everything considered left of the screen; title, search bar and mod list. - * - * @param mouseX the current mouse x position - * @param mouseY the current mouse y position - * @param partialTicks the partial ticks - */ - private void drawModList(int mouseX, int mouseY, float partialTicks) { - GlStateManager.enableBlend(); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - this.mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); - drawModalRectWithCustomSizedTexture(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); - GlStateManager.disableBlend(); - this.modList.drawScreen(mouseX, mouseY, partialTicks); - this.searchTextField.drawTextBox(); - - String modsLabel = TextFormatting.BOLD + I18n.format("catalogue.gui.title"); - String countLabel = TextFormatting.GRAY + "(" + CACHED_MODS.size() + ")"; - String title = modsLabel + " " + countLabel; - int titleWidth = this.fontRenderer.getStringWidth(title); - int titleLeft = this.modList.left + (this.modList.width - titleWidth) / 2; - drawString(this.fontRenderer, title, titleLeft, 10, 0xFFFFFF); - - int countLabelWidth = this.fontRenderer.getStringWidth(countLabel); - if (ClientHelper.isMouseWithin(titleLeft + titleWidth - countLabelWidth, 10, countLabelWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { - Pair counts = COUNTS.get(); - List lines = List.of( - I18n.format("catalogue.gui.mod_count", counts.getLeft()), - I18n.format("catalogue.gui.library_count", counts.getRight()) - ); - this.setActiveTooltip(lines); - this.tooltipYOffset = 10; - } - - if (ClientHelper.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("catalogue.gui.filter_updates")); - this.tooltipYOffset = 10; + @Override + public void updatePosition(int slotIndex, int x, int y, float partialTicks) { } } @@ -825,6 +709,119 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { } } + private class StringList extends CatalogueListExtended { + private List entries = Lists.newArrayList(); + + public StringList(int width, int height, int left, int top) { + super(CatalogueModListScreen.this.mc, width, height, top, top + height, 10); + this.setSlotXBoundsFromLeft(left + 8); + this.visible = false; + } + + public void setTextFromInfo(ModContainer data) { + this.entries.clear(); + this.visible = true; + if (data.getMetadata().description.trim().isBlank()) { + this.visible = false; + return; + } + String description = data.getMetadata().description.trim(); + List lines = CatalogueModListScreen.this.fontRenderer.listFormattedStringToWidth(description, this.getListWidth()); + + for (String line : lines) { + String cleanLine = line.replace("\n", "").replace("\r", "").trim(); + this.entries.add(new StringEntry(cleanLine)); + } + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + ClientHelper.scissor(this.left, this.top, this.width, this.bottom - this.top); + super.drawScreen(mouseX, mouseY, partialTicks); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + } + + @Override + protected void drawContainerBackground(@Nullable Tessellator tessellator) { + int x = this.left; + int y = this.top; + int width = this.width; + int height = this.height; + drawRect(x, y + 1, x + 1, y + height - 1, 0x77000000); + drawRect(x + 1, y, x + width - 1, y + height, 0x77000000); + drawRect(x + width - 1, y + 1, x + width, y + height - 1, 0x77000000); + } + + @Override + protected int getScrollBarX() { + return this.left + this.width - 7; + } + + @Override + public int getListLeft() { + return this.left + 8; + } + + @Override + public int getListWidth() { + return this.width - 16; + } + + @Override + protected int getRowTop(int $$0) { + return super.getRowTop($$0) + 4; + } + + @Override + public int getMaxScroll() { + return Math.max(0, this.getContentHeight() - (this.height - 12)); + } + + @Override + protected int getSize() { + return this.entries.size(); + } + + @Override + public IGuiListEntry getListEntry(int index) { + return this.entries.get(index); + } + + @Override + protected void drawTopAndBottom(Tessellator tessellator) { + } + + @Override + protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { + } + } + + private class StringEntry implements CatalogueListExtended.IGuiListEntry { + private String line; + + public StringEntry(String line) { + this.line = line; + } + + @Override + public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { + drawString(CatalogueModListScreen.this.fontRenderer, this.line, left, top, 0xFFFFFF); + } + + @Override + public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { + return true; + } + + @Override + public void updatePosition(int slotIndex, int x, int y, float partialTicks) { + } + + @Override + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { + } + } + /** * Draws a string and prepends a label. If the formed string and label is longer than the * specified max width, it will automatically be trimmed and allows the user to hover the @@ -1169,12 +1166,6 @@ private void openLink(String url) { this.handleComponentClick(new TextComponentString("").setStyle(style)); } - private record Dimension(int width, int height) { - } - - private record ImageInfo(ResourceLocation resource, Dimension size) { - } - private boolean shouldDraw(ForgeVersion.CheckResult update) { return update != null && update.status.shouldDraw(); } @@ -1184,4 +1175,10 @@ private boolean shouldUpdate(ForgeVersion.CheckResult update) { ForgeVersion.Status status = update.status; return status == ForgeVersion.Status.OUTDATED || status == ForgeVersion.Status.BETA_OUTDATED; } + + private record Dimension(int width, int height) { + } + + private record ImageInfo(ResourceLocation resource, Dimension size) { + } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index 2fbf78fce..279883cb4 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -10,8 +10,6 @@ import org.lwjgl.opengl.GL11; public abstract class CatalogueListExtended extends GuiListExtended { - // Noting different from the original one, but allows you to remove the shadow on the bottom and top. - // Created to avoid mixins. private boolean scrollBarVisible; public CatalogueListExtended(Minecraft mcIn, int widthIn, int heightIn, int topIn, int bottomIn, int slotHeightIn) { From a57373b9ca6c4770984556c0bb5b28688f409024 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:51:06 +0800 Subject: [PATCH 37/91] Unnecessary to register scroll buttons --- .../catalogue/client/screen/CatalogueModListScreen.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 5b12890d6..b46b4e804 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -107,7 +107,6 @@ public void initGui() { this.modList = new ModList(); this.modList.setSlotXBoundsFromLeft(10); - this.modList.registerScrollButtons(7, 8); this.buttonList.add(new GuiButton(1, 10, modList.bottom + 8, 127, 20, I18n.format("gui.back"))); this.modFolderButton = this.addButton(new CatalogueIconButton(2, 140, modList.bottom + 8, 0, 0)); @@ -127,7 +126,6 @@ public void initGui() { this.issueButton.visible = false; this.descriptionList = new StringList(contentWidth + padding * 2, 50, contentLeft - padding, 130); - this.descriptionList.registerScrollButtons(9, 10); this.updatesButton = this.addButton(new CatalogueCheckBoxButton(6, this.modList.right - 14, 7, false)); From 3b55025793e87366caddedbaa823efe1fc3a589e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 29 Aug 2025 20:37:35 +0800 Subject: [PATCH 38/91] Update ClientHelper.java --- src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java index 3a73a71b3..507429f7e 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java @@ -1,7 +1,6 @@ package com.cleanroommc.catalogue.client; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.ScaledResolution; import org.lwjgl.opengl.GL11; From ad6ba81289f177a521ec3f6dd279202e4cc89253 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 30 Aug 2025 11:30:36 +0800 Subject: [PATCH 39/91] Optimize Ctrl+F to focus Make it more standard Sort imports --- .../client/screen/CatalogueModListScreen.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index b46b4e804..9bf943148 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -1,9 +1,14 @@ package com.cleanroommc.catalogue.client.screen; import com.cleanroommc.catalogue.CatalogueConfig; +import com.cleanroommc.catalogue.CatalogueConstants; +import com.cleanroommc.catalogue.client.ClientHelper; +import com.cleanroommc.catalogue.client.screen.widget.CatalogueCheckBoxButton; +import com.cleanroommc.catalogue.client.screen.widget.CatalogueIconButton; +import com.cleanroommc.catalogue.client.screen.widget.CatalogueListExtended; +import com.cleanroommc.catalogue.client.screen.widget.CatalogueTextField; import com.google.common.base.Suppliers; import com.google.common.collect.Lists; -import com.cleanroommc.catalogue.CatalogueConstants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiOptions; @@ -29,11 +34,6 @@ import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.client.IModGuiFactory; -import com.cleanroommc.catalogue.client.ClientHelper; -import com.cleanroommc.catalogue.client.screen.widget.CatalogueCheckBoxButton; -import com.cleanroommc.catalogue.client.screen.widget.CatalogueIconButton; -import com.cleanroommc.catalogue.client.screen.widget.CatalogueListExtended; -import com.cleanroommc.catalogue.client.screen.widget.CatalogueTextField; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.ModMetadata; @@ -274,19 +274,11 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti } @Override - public void handleKeyboardInput() throws IOException { - if (Keyboard.getEventKey() == Keyboard.KEY_F && (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL))) { - if (!this.searchTextField.isFocused()) { - this.searchTextField.setFocused(true); - } + protected void keyTyped(char typedChar, int key) throws IOException { + if (isKeyComboCtrlF(key) && !this.searchTextField.isFocused()) { + this.searchTextField.setFocused(true); return; } - - super.handleKeyboardInput(); - } - - @Override - protected void keyTyped(char typedChar, int key) throws IOException { if (this.searchTextField.textboxKeyTyped(typedChar, key)) { String s = this.searchTextField.getText(); this.updateSearchField(s); @@ -1164,16 +1156,20 @@ private void openLink(String url) { this.handleComponentClick(new TextComponentString("").setStyle(style)); } - private boolean shouldDraw(ForgeVersion.CheckResult update) { + private static boolean shouldDraw(ForgeVersion.CheckResult update) { return update != null && update.status.shouldDraw(); } - private boolean shouldUpdate(ForgeVersion.CheckResult update) { + private static boolean shouldUpdate(ForgeVersion.CheckResult update) { if (update == null) return false; ForgeVersion.Status status = update.status; return status == ForgeVersion.Status.OUTDATED || status == ForgeVersion.Status.BETA_OUTDATED; } + private static boolean isKeyComboCtrlF(int keyID) { + return keyID == Keyboard.KEY_F && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown(); + } + private record Dimension(int width, int height) { } From 4939ee0f611ab4a60583f147ac147642301d3dea Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 30 Aug 2025 11:42:16 +0800 Subject: [PATCH 40/91] Optimize search text field logic --- .../catalogue/client/screen/CatalogueModListScreen.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 9bf943148..7aed94dc1 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -238,11 +238,13 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); if (shouldUpdate(update) && update.homepage != null) { this.openLink(update.homepage); + return; } } } // Search Text Field + this.searchTextField.mouseClicked(mouseX, mouseY, button); if (ClientHelper.isMouseWithin(this.searchTextField.x, this.searchTextField.y, this.searchTextField.width, this.searchTextField.height, mouseX, mouseY)) { // Right click to empty if (button == 1) { @@ -268,7 +270,6 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti this.lastClickTime = currentTine; } } - this.searchTextField.mouseClicked(mouseX, mouseY, button); super.mouseClicked(mouseX, mouseY, button); } From fea4d690205597b50d149e5d6796850826fe1a88 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:44:23 +0800 Subject: [PATCH 41/91] Optimize in-game background drawing --- .../catalogue/client/ClientHelper.java | 4 ++++ .../client/screen/CatalogueModListScreen.java | 24 +++++++++++++------ .../screen/widget/CatalogueListExtended.java | 4 ++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java index 507429f7e..f747472ba 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java @@ -32,4 +32,8 @@ public static void scissor(int screenX, int screenY, int boxWidth, int boxHeight public static boolean isMouseWithin(int x, int y, int width, int height, int mouseX, int mouseY) { return mouseX >= x && mouseX < x + width && mouseY >= y && mouseY < y + height; } + + public static boolean isPlayingGame() { + return Minecraft.getMinecraft().player != null; + } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 7aed94dc1..03b242655 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -42,6 +42,7 @@ import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.GL11; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.awt.Desktop; import java.awt.image.BufferedImage; @@ -140,7 +141,7 @@ public void initGui() { this.modList.centerScrollOn(entry); } } - this.updateSearchField(this.searchTextField.getText()); + this.updateSearchFieldSuggestion(this.searchTextField.getText()); } @Override @@ -249,7 +250,7 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti // Right click to empty if (button == 1) { this.searchTextField.setText(""); - this.updateSearchField(""); + this.updateSearchFieldSuggestion(""); this.modList.filterAndUpdateList(""); lastSearch = ""; return; @@ -261,7 +262,7 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti if (!text.isEmpty() && currentTine - this.lastClickTime < 250L && !this.searchTextField.getIsTextTruncated()) { text += this.searchTextField.getSuggestion(); this.searchTextField.setText(text); - this.updateSearchField(text); + this.updateSearchFieldSuggestion(text); this.modList.filterAndUpdateList(text); lastSearch = text; this.lastClickTime = currentTine; @@ -282,7 +283,7 @@ protected void keyTyped(char typedChar, int key) throws IOException { } if (this.searchTextField.textboxKeyTyped(typedChar, key)) { String s = this.searchTextField.getText(); - this.updateSearchField(s); + this.updateSearchFieldSuggestion(s); this.modList.filterAndUpdateList(s); this.updateSelectedModList(); lastSearch = s; @@ -339,7 +340,7 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { } private class ModList extends CatalogueListExtended { - private List entries = Lists.newArrayList(); + private List entries = Lists.newArrayList(); private int selectedIndex = -1; public ModList() { @@ -434,6 +435,15 @@ protected boolean isSelected(int slotIndex) { return this.selectedIndex == slotIndex; } + @Override + protected void drawContainerBackground(@Nonnull Tessellator tessellator) { + if (ClientHelper.isPlayingGame()) { + drawRect(this.left, this.top, this.right, this.bottom, 0x66000000); + return; + } + super.drawContainerBackground(tessellator); + } + @Override protected void drawTopAndBottom(Tessellator tessellator) { } @@ -701,7 +711,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { } private class StringList extends CatalogueListExtended { - private List entries = Lists.newArrayList(); + private List entries = Lists.newArrayList(); public StringList(int width, int height, int left, int top) { super(CatalogueModListScreen.this.mc, width, height, top, top + height, 10); @@ -1117,7 +1127,7 @@ private void updateSelectedModList() { } } - private void updateSearchField(String value) { + private void updateSearchFieldSuggestion(String value) { if (value.isEmpty()) { this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search")); } else { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index 279883cb4..3e2565b8f 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -76,13 +76,13 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { protected void drawTopAndBottom(Tessellator tessellator) { BufferBuilder buffer = tessellator.getBuffer(); - buffer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); buffer.pos(this.left, this.top + 4, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 0).endVertex(); buffer.pos(this.right, this.top + 4, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 0).endVertex(); buffer.pos(this.right, this.top, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 255).endVertex(); buffer.pos(this.left, this.top, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 255).endVertex(); tessellator.draw(); - buffer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); buffer.pos(this.left, this.bottom, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 255).endVertex(); buffer.pos(this.right, this.bottom, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 255).endVertex(); buffer.pos(this.right, this.bottom - 4, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 0).endVertex(); From 60980919d7b47130a92ccb14a0508abca4387ffe Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:24:52 +0800 Subject: [PATCH 42/91] =?UTF-8?q?Support=20"=EF=BC=9A"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It just works --- .../catalogue/client/screen/CatalogueModListScreen.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 03b242655..22727710f 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -627,7 +627,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { drawString(this.fontRenderer, this.selectedModData.getName(), 0, 0, 0xFFFFFF); GlStateManager.popMatrix(); - // Draw version + // Draw mod id String modId = TextFormatting.DARK_GRAY + I18n.format("catalogue.gui.mod_id", this.selectedModData.getModId()); int modIdWidth = this.fontRenderer.getStringWidth(modId); drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); @@ -839,8 +839,10 @@ public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relat @SuppressWarnings("SameParameterValue") private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { String formatted = I18n.format(format, text); // Attempting to keep Forge's lang since it's already support many languages - String label = formatted.substring(0, formatted.indexOf(":") + 1); - String content = formatted.substring(formatted.indexOf(":") + 1); + String colon = ":"; + if (formatted.contains(":")) colon = ":"; + String label = formatted.substring(0, formatted.indexOf(colon) + 1); + String content = formatted.substring(formatted.indexOf(colon) + 1); if (this.fontRenderer.getStringWidth(formatted) > maxWidth) { content = this.fontRenderer.trimStringToWidth(content, maxWidth - this.fontRenderer.getStringWidth(label) - 7) + "..."; String credits = labelColor + label; From 1995e09e494829f7dddd9838940344c4471f3347 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:55:49 +0800 Subject: [PATCH 43/91] Update CatalogueModListScreen.java --- .../catalogue/client/screen/CatalogueModListScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 22727710f..467103594 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -1131,7 +1131,7 @@ private void updateSelectedModList() { private void updateSearchFieldSuggestion(String value) { if (value.isEmpty()) { - this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search")); + this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search") + "..."); } else { Optional optional = CACHED_MODS.values().stream().filter(data -> { return data.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); From 563c7de3704acc530a00b9a56a5c2dfdcd867ab8 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 30 Aug 2025 22:06:55 +0800 Subject: [PATCH 44/91] Update CatalogueIconButton.java --- .../client/screen/widget/CatalogueIconButton.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index 70a14d6f3..e669be141 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -7,7 +7,6 @@ import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; -import com.cleanroommc.catalogue.client.ClientHelper; /** * Author: MrCrayfish @@ -22,8 +21,16 @@ public CatalogueIconButton(int id, int x, int y, int u, int v) { this(id, x, y, u, v, 20, ""); } + public CatalogueIconButton(int id, int x, int y, int u, int v, int width, int height) { + this(id, x, y, u, v, width, height, ""); + } + public CatalogueIconButton(int id, int x, int y, int u, int v, int width, String label) { - super(id, x, y, width, 20, ""); + this(id, x, y, u, v, width, 20, label); + } + + public CatalogueIconButton(int id, int x, int y, int u, int v, int width, int height, String label) { + super(id, x, y, width, height, ""); this.label = label; this.u = u; this.v = v; @@ -40,7 +47,7 @@ public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partia GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); int contentWidth = 10 + fontrenderer.getStringWidth(this.label) + (!this.label.isEmpty() ? 4 : 0); int iconX = this.x + (this.width - contentWidth) / 2; - int iconY = this.y + 5; + int iconY = this.y + (this.height - 10) / 2; float brightness = this.enabled ? 1.0F : 0.5F; GlStateManager.color(brightness, brightness, brightness, 1.0F); drawModalRectWithCustomSizedTexture(iconX, iconY, this.u, this.v, 10, 10, 64, 64); @@ -51,7 +58,7 @@ public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partia } - private int getFGColor(){ + private int getFGColor() { if (packedFGColour != 0) { return packedFGColour; } else if (!this.enabled) { From 88ba37df1b94fb8be74844f4c4023de46b08df79 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 30 Aug 2025 22:10:51 +0800 Subject: [PATCH 45/91] Optimize imports --- src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java | 1 - .../catalogue/client/screen/widget/CatalogueCheckBoxButton.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index 99fabb1d2..e4a332255 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -1,6 +1,5 @@ package com.cleanroommc.catalogue; -import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; import net.minecraftforge.common.config.Config; import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.fml.client.event.ConfigChangedEvent; diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java index d6984e5bf..3ac25d992 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java @@ -5,7 +5,6 @@ import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; -import com.cleanroommc.catalogue.client.ClientHelper; /** * Author: MrCrayfish From 3b2b735bb3dfcf640c68cc31acb67043c4fa58a9 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 30 Aug 2025 22:21:09 +0800 Subject: [PATCH 46/91] =?UTF-8?q?Remove=20all=20the=20"=EF=BC=9A"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It works bad... --- .../client/screen/CatalogueModListScreen.java | 6 ++---- .../resources/assets/catalogue/lang/zh_cn.lang | 14 +++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 467103594..138a59a1d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -839,10 +839,8 @@ public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relat @SuppressWarnings("SameParameterValue") private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { String formatted = I18n.format(format, text); // Attempting to keep Forge's lang since it's already support many languages - String colon = ":"; - if (formatted.contains(":")) colon = ":"; - String label = formatted.substring(0, formatted.indexOf(colon) + 1); - String content = formatted.substring(formatted.indexOf(colon) + 1); + String label = formatted.substring(0, formatted.indexOf(":") + 1); + String content = formatted.substring(formatted.indexOf(":") + 1); if (this.fontRenderer.getStringWidth(formatted) > maxWidth) { content = this.fontRenderer.trimStringToWidth(content, maxWidth - this.fontRenderer.getStringWidth(label) - 7) + "..."; String credits = labelColor + label; diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index 7990c432c..14b411969 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -8,16 +8,16 @@ catalogue.gui.no_mods=没有模组… catalogue.gui.open_mods_folder=打开 Mods 文件夹 catalogue.gui.filter_updates=仅显示有更新的模组 catalogue.gui.info=此菜单由 Catalogue 提供。单击此处打开此模组的 CurseForge 页面! -catalogue.gui.version=版本:%s -catalogue.gui.mod_id=Mod ID:%s -catalogue.gui.authors=作者:%s -catalogue.gui.credits=致谢:%s -catalogue.gui.license=许可协议:%s +catalogue.gui.version=版本: %s +catalogue.gui.mod_id=Mod ID: %s +catalogue.gui.authors=作者: %s +catalogue.gui.credits=致谢: %s +catalogue.gui.license=许可协议: %s catalogue.gui.beta=此版本为 Beta 版 catalogue.gui.ahead=此版本高于搜索到的最新版(%s) -catalogue.gui.update_available=更新(%s)可用:%s +catalogue.gui.update_available=更新(%s)可用: %s catalogue.gui.update_available_no_page=更新(%s)可用 -catalogue.gui.beta_update_available=Beta 更新(%s)可用:%s +catalogue.gui.beta_update_available=Beta 更新(%s)可用: %s catalogue.gui.beta_update_available_no_page=Beta 更新(%s)可用 catalogue.gui.mod_count=%s个模组 catalogue.gui.library_count=%s个库 From f486cb92aaca6a14d23dec0bdef98368961e5d03 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 31 Aug 2025 17:13:37 +0800 Subject: [PATCH 47/91] Update CatalogueModListScreen.java --- .../catalogue/client/screen/CatalogueModListScreen.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 138a59a1d..9568d75b6 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -53,6 +53,7 @@ import java.util.stream.Collectors; public class CatalogueModListScreen extends GuiScreen { + private static final Comparator SORT = Comparator.comparing(o -> o.getData().getName()); private static final ResourceLocation MISSING_BANNER = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/missing_banner.png"); private static final ResourceLocation MISSING_BACKGROUND = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/missing_background.png"); private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); @@ -373,7 +374,7 @@ public void filterAndUpdateList(String text) { .filter(data -> data.getName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) .filter(data -> !updatesButton.selected() || shouldUpdate(ForgeVersion.getCleanResult(data))) .map(data -> new ModListEntry(data, this)) - .sorted(Comparator.comparing(entry -> entry.data.getName())) + .sorted(SORT) .collect(Collectors.toList()); this.entries = entries; this.selectMod(this.getEntryFromInfo(selectedModData)); @@ -590,6 +591,10 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEven return true; } + public ModContainer getData() { + return this.data; + } + @Override public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { } From 61f96d250ee6314da68eb7a2f27d111e15ffe009 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 3 Oct 2025 02:03:18 +0800 Subject: [PATCH 48/91] Cleanroom page Eye candy. Cool. --- .../cleanroommc/common/CleanroomContainer.java | 4 +++- src/main/resources/cleanroom_background.png | Bin 0 -> 103176 bytes src/main/resources/cleanroom_banner.png | Bin 0 -> 58938 bytes 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/cleanroom_background.png create mode 100644 src/main/resources/cleanroom_banner.png diff --git a/src/main/java/com/cleanroommc/common/CleanroomContainer.java b/src/main/java/com/cleanroommc/common/CleanroomContainer.java index 4a7f9db9d..ba41180d6 100644 --- a/src/main/java/com/cleanroommc/common/CleanroomContainer.java +++ b/src/main/java/com/cleanroommc/common/CleanroomContainer.java @@ -19,7 +19,9 @@ public CleanroomContainer() { """; meta.version = CleanroomVersion.VERSION; meta.authorList = Arrays.asList("LexManos", "cpw", "fry", "Rongmario", "kappa_maintainer", "Li"); - meta.logoFile = "/cleanroom_icon.png"; + meta.logoFile = "/cleanroom_banner.png"; + meta.iconFile = "/cleanroom_icon.png"; + meta.backgroundFile = "/cleanroom_background.png"; } @Override diff --git a/src/main/resources/cleanroom_background.png b/src/main/resources/cleanroom_background.png new file mode 100644 index 0000000000000000000000000000000000000000..48e12821e52a61d4a97a75a7a350e977cad81f22 GIT binary patch literal 103176 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/cleanroom_banner.png b/src/main/resources/cleanroom_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..ddc6f701ac65ffb96bb9f9cf06d016c1bec405ee GIT binary patch literal 58938 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 literal 0 HcmV?d00001 From 6d7f3a6af2a08477d700859780ebe901ae3617dc Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 3 Oct 2025 19:30:13 +0800 Subject: [PATCH 49/91] Sync changes --- build.gradle | 1 + .../catalogue/CatalogueConfig.java | 39 +- .../java/com/cleanroommc/catalogue/Utils.java | 39 + .../catalogue/client/Branding.java | 64 + .../catalogue/client/CleanroomModData.java | 226 ++++ .../catalogue/client/ClientHelper.java | 104 ++ .../catalogue/client/IModData.java | 97 ++ .../catalogue/client/ImageInfo.java | 9 + .../catalogue/client/ImagePredicate.java | 31 + .../client/screen/CatalogueModListScreen.java | 1139 ++++++++++------- .../client/screen/DropdownMenuHandler.java | 11 + .../client/screen/MinecraftModData.java | 133 ++ .../client/screen/layout/AbstractLayout.java | 91 ++ .../screen/layout/BorderedLinearLayout.java | 54 + .../client/screen/layout/Divisor.java | 51 + .../client/screen/layout/GridLayout.java | 227 ++++ .../client/screen/layout/Layout.java | 24 + .../client/screen/layout/LayoutElement.java | 32 + .../client/screen/layout/LayoutSettings.java | 150 +++ .../client/screen/layout/LinearLayout.java | 119 ++ .../client/screen/layout/ScreenRectangle.java | 11 + .../widget/CatalogueCheckBoxButton.java | 53 - .../screen/widget/CatalogueIconButton.java | 23 +- .../screen/widget/CatalogueListExtended.java | 20 +- .../screen/widget/CatalogueTextButton.java | 78 ++ .../screen/widget/CatalogueTextField.java | 61 +- .../client/screen/widget/DropdownMenu.java | 488 +++++++ .../client/screen/widget/WidgetSprites.java | 25 + .../InvalidBrandingImageException.java | 10 + .../ModResourceNotFoundException.java | 10 + .../platform/CleanroomPlatformHelper.java | 59 + .../catalogue/platform/ClientServices.java | 16 + .../platform/services/IPlatformHelper.java | 26 + .../common/CatalogueContainer.java | 2 - .../fml/common/ModMetadata.java | 2 - ...atalogue.platform.services.IPlatformHelper | 1 + .../assets/catalogue/lang/de_de.lang | 2 + .../assets/catalogue/lang/en_au.lang | 50 + .../assets/catalogue/lang/en_us.lang | 57 +- .../assets/catalogue/lang/pl_pl.lang | 2 + .../assets/catalogue/lang/zh_cn.lang | 63 +- .../assets/catalogue/textures/gui/icons.png | Bin 831 -> 965 bytes .../textures/gui/sprites/dropdown/item.png | Bin 0 -> 166 bytes .../gui/sprites/dropdown/item_highlighted.png | Bin 0 -> 170 bytes .../textures/gui/sprites/widget/button.png | Bin 0 -> 4022 bytes .../gui/sprites/widget/button_disabled.png | Bin 0 -> 2959 bytes .../gui/sprites/widget/button_highlighted.png | Bin 0 -> 6156 bytes src/main/resources/forge_at.cfg | 1 + 48 files changed, 3112 insertions(+), 589 deletions(-) create mode 100644 src/main/java/com/cleanroommc/catalogue/Utils.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/Branding.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/IModData.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/DropdownMenuHandler.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/AbstractLayout.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/BorderedLinearLayout.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/Divisor.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/GridLayout.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/Layout.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutElement.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutSettings.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/layout/ScreenRectangle.java delete mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/widget/WidgetSprites.java create mode 100644 src/main/java/com/cleanroommc/catalogue/exception/InvalidBrandingImageException.java create mode 100644 src/main/java/com/cleanroommc/catalogue/exception/ModResourceNotFoundException.java create mode 100644 src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java create mode 100644 src/main/java/com/cleanroommc/catalogue/platform/ClientServices.java create mode 100644 src/main/java/com/cleanroommc/catalogue/platform/services/IPlatformHelper.java create mode 100644 src/main/resources/META-INF/services/com.cleanroommc.catalogue.platform.services.IPlatformHelper create mode 100644 src/main/resources/assets/catalogue/lang/de_de.lang create mode 100644 src/main/resources/assets/catalogue/lang/en_au.lang create mode 100644 src/main/resources/assets/catalogue/lang/pl_pl.lang create mode 100644 src/main/resources/assets/catalogue/textures/gui/sprites/dropdown/item.png create mode 100644 src/main/resources/assets/catalogue/textures/gui/sprites/dropdown/item_highlighted.png create mode 100644 src/main/resources/assets/catalogue/textures/gui/sprites/widget/button.png create mode 100644 src/main/resources/assets/catalogue/textures/gui/sprites/widget/button_disabled.png create mode 100644 src/main/resources/assets/catalogue/textures/gui/sprites/widget/button_highlighted.png diff --git a/build.gradle b/build.gradle index c31ddce37..12dac10f7 100644 --- a/build.gradle +++ b/build.gradle @@ -417,6 +417,7 @@ project(':cleanroom') { dependencies { compileOnly "com.cleanroommc:lwjglx:1.0.0" + compileOnly "org.jetbrains:annotations:24.0.0" installer "com.cleanroommc:lwjglxx:1.1.5" lwjglLibraries[0].each { installer "org.lwjgl:$it:$props.lwjgl_version" diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index e4a332255..d823cf401 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -15,7 +15,7 @@ public class CatalogueConfig { "They will have grey names in the mod list." }) @Config.LangKey("catalogue.config.library_list") - public static String[] libraryList = new String[] { + public static String[] libraryList = new String[]{ "minecraft", "forge", "FML", @@ -27,6 +27,43 @@ public class CatalogueConfig { "scalar" }; + @Config.RequiresMcRestart + @Config.Comment({ + "The list of ignored dependencies' mod ids.", + "They will not be displayed when searching for dependencies/dependants." + }) + @Config.LangKey("catalogue.config.ignored_dependencies_list") + public static String[] ignoredDependenciesList = new String[]{ + "minecraft", + "forge", + "FML", + "mcp", + "cleanroom" + }; + + @Config.RequiresMcRestart + @Config.Comment({ + "Whether limit the size of mods' banners." + }) + @Config.LangKey("catalogue.config.enable_banner_limit") + public static boolean enableBannerLimit = false; + + @Config.RequiresMcRestart + @Config.Comment({ + "The maximum of banner's width. Will not work if Enable Banner Limit is set false." + }) + @Config.LangKey("catalogue.config.banner_max_width") + @Config.RangeInt(min = 0) + public static int bannerMaxWidth = 1280; + + @Config.RequiresMcRestart + @Config.Comment({ + "The maximum of banner's height. Will not work if Enable Banner Limit is set false." + }) + @Config.LangKey("catalogue.config.banner_max_height") + @Config.RangeInt(min = 0) + public static int bannerMaxHeight = 256; + @SubscribeEvent public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) { if (event.getModID().equals(CatalogueConstants.MOD_ID)) { diff --git a/src/main/java/com/cleanroommc/catalogue/Utils.java b/src/main/java/com/cleanroommc/catalogue/Utils.java new file mode 100644 index 000000000..977b45da0 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/Utils.java @@ -0,0 +1,39 @@ +package com.cleanroommc.catalogue; + +import net.minecraft.util.ResourceLocation; + +import java.util.function.Consumer; + +/** + * Author: MrCrayfish + */ +public class Utils { + public static ResourceLocation resource(String name) { + return new ResourceLocation(CatalogueConstants.MOD_ID, name); + } + + public static ResourceLocation withDefaultNamespace(String name) { + return resource("textures/gui/sprites/" + name + ".png"); + } + + public static T make(T object, Consumer consumer) { + consumer.accept(object); + return object; + } + + public static float lerp(float delta, float start, float end) { + return start + delta * (end - start); + } + + public static double lerp(double delta, double start, double end) { + return start + delta * (end - start); + } + + public static int roundToward(int value, int factor) { + return positiveCeilDiv(value, factor) * factor; + } + + public static int positiveCeilDiv(int x, int y) { + return -Math.floorDiv(-x, y); + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/Branding.java b/src/main/java/com/cleanroommc/catalogue/client/Branding.java new file mode 100644 index 000000000..dd1b52407 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/Branding.java @@ -0,0 +1,64 @@ +package com.cleanroommc.catalogue.client; + +import com.cleanroommc.catalogue.CatalogueConstants; +import com.cleanroommc.catalogue.Utils; +import com.cleanroommc.catalogue.exception.InvalidBrandingImageException; +import com.cleanroommc.catalogue.exception.ModResourceNotFoundException; +import com.cleanroommc.catalogue.platform.ClientServices; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.client.resources.IResourcePack; +import net.minecraft.util.ResourceLocation; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Optional; +import java.util.function.BiPredicate; +import java.util.function.Function; + +/** + * Author: MrCrayfish + */ +public record Branding(String prefix, int imageWidth, int imageHeight, + BiPredicate predicate, + Function locator, boolean override) { + public static final Branding ICON = new Branding("icon", 256, 256, ImagePredicate.SQUARE.and(ImagePredicate.LESS_THAN_OR_EQUAL), IModData::getImageIcon, false); + public static final Branding BANNER = new Branding("banner", ClientServices.PLATFORM.getBannerLimit().maxWidth(), ClientServices.PLATFORM.getBannerLimit().maxHeight(), ClientServices.PLATFORM.getEnableBannerLimit() ? ImagePredicate.LESS_THAN_OR_EQUAL : ImagePredicate.ANY, IModData::getBanner, false); + public static final Branding BACKGROUND = new Branding("background", 512, 256, ImagePredicate.EQUAL, IModData::getBackground, true); + + public Optional loadResource(IModData data) { + String resource = this.locator.apply(data); + if (resource.isBlank()) return Optional.empty(); + + String modId = data.getModId(); + BufferedImage image = null; + try { + IResourcePack resourcePack = data.getResourcePack(); + if (this.equals(Branding.BANNER) && resourcePack != null && !resource.startsWith("/")) { + image = resourcePack.getPackImage(); + } else { + resource = resource.startsWith("/") ? resource : "/" + resource; + image = ClientServices.PLATFORM.loadImageFromModResource(modId, resource); + } + this.predicate.test(image, this); // An InvalidBrandingImageException will be thrown if anything is wrong + DynamicTexture texture = new DynamicTexture(image); + ResourceLocation id = this.override ? Utils.resource(this.prefix) : + Utils.resource("%s/%s".formatted(this.prefix, data.getModId())); + Minecraft.getMinecraft().getTextureManager().loadTexture(id, texture); + return Optional.of(new ImageInfo(id, image.getWidth(), image.getHeight(), () -> { + Minecraft.getMinecraft().getTextureManager().deleteTexture(id); + })); + } catch (InvalidBrandingImageException e) { + CatalogueConstants.LOG.error("Invalid {} branding resource '{}' for mod '{}'", this.prefix, resource, modId, e); + } catch (ModResourceNotFoundException e) { + CatalogueConstants.LOG.error("Unable to locate the {} branding resource '{}' for mod '{}'", this.prefix, resource, modId, e); + } catch (IOException e) { + CatalogueConstants.LOG.error("An error occurred when loading the {} branding resource '{}' for mod '{}'", this.prefix, resource, modId, e); + } + + return Optional.empty(); + } + + public record BannerLimit(int maxWidth, int maxHeight) { + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java new file mode 100644 index 000000000..b1426ef61 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -0,0 +1,226 @@ +package com.cleanroommc.catalogue.client; + +import com.cleanroommc.catalogue.CatalogueConfig; +import com.cleanroommc.catalogue.CatalogueConstants; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.resources.IResourcePack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.client.FMLClientHandler; +import net.minecraftforge.fml.client.IModGuiFactory; +import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.ModMetadata; +import net.minecraftforge.fml.common.versioning.ArtifactVersion; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Author: MrCrayfish + */ +public class CleanroomModData implements IModData { + public static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation("forge", "textures/gui/version_check_icons.png"); + public static final List LIB_MODS = Arrays.asList(CatalogueConfig.libraryList); + public static final List IGNORED_DEPENDENCIES = Arrays.asList(CatalogueConfig.ignoredDependenciesList); + + private final ModContainer info; + private final Type type; + private final Set dependencies; + + public CleanroomModData(ModContainer info) { + this.info = info; + this.type = analyzeType(info); + this.dependencies = this.analyzeDependencies(info); + } + + @Override + public Type getType() { + return this.type; + } + + @Override + public String getModId() { + return this.info.getModId(); + } + + @Override + public String getDisplayName() { + return this.info.getName(); + } + + @Override + public @NotNull String getVersion() { + return this.info.getDisplayVersion(); + } + + @Override + public @NotNull String getInnerVersion() { + return this.info.getVersion(); + } + + @Override + public @NotNull String getDescription() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.description : ""; + } + + @Override + public @NotNull String getItemIcon() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.iconItem : ""; + } + + @Override + public @NotNull String getImageIcon() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.iconFile : ""; + } + + @Override + public @NotNull String getLicense() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.license : ""; + } + + @Override + public @NotNull String getCredits() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.credits : ""; + } + + @Override + public @NotNull String getAuthors() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.getAuthorList() : ""; + } + + @Override + public @NotNull String getHomepage() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.url : ""; + } + + @Override + public @NotNull String getIssueTracker() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.issueTrackerUrl : ""; + } + + @Override + public @NotNull String getBanner() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.logoFile : ""; + } + + @Override + public @NotNull String getBackground() { + ModMetadata metadata = this.getMetadata(); + return metadata != null ? metadata.backgroundFile : ""; + } + + @Override + public @Nullable Update getUpdate() { + ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.info); + if (result != null && result.status.shouldDraw()) { + return new Update(result.status.isAnimated(), result.url, result.status.getSheetOffset(), VERSION_CHECK_ICONS, result.status == ForgeVersion.Status.OUTDATED || result.status == ForgeVersion.Status.BETA_OUTDATED, result.latestFound, result.homepage); + } + return null; + } + + @Override + public Set getDependencies() { + return this.dependencies; + } + + @Override + public boolean hasConfig() { + if (this.info == null) return false; + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(this.info); + if (guiFactory == null) return false; + return guiFactory.hasConfigGui(); + } + + @Override + public boolean isLibrary() { + return this.info.getModId().equals("forge") || this.type != Type.DEFAULT; + } + + @Override + public void openConfigScreen(Minecraft minecraft, GuiScreen parent) { + try { + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(this.info); + GuiScreen newScreen = guiFactory.createConfigGui(parent); + minecraft.displayGuiScreen(newScreen); + } catch (Exception e) { + CatalogueConstants.LOG.error("There was a critical issue trying to build the config GUI for {}", this.getModId(), e); + } + } + + @Override + public void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y) { + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + int vOffset = update.animated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; + minecraft.getTextureManager().bindTexture(update.textures()); + Gui.drawModalRectWithCustomSizedTexture(x, y, update.texOffset() * 8, vOffset, 8, 8, 64, 16); + } + + @Override + public @NotNull String getUpdateText(Update update) { + ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.info); + if (result == null) return ""; + switch (result.status) { + case BETA: + return TextFormatting.GOLD + I18n.format("catalogue.gui.beta"); + case AHEAD: + return TextFormatting.LIGHT_PURPLE + I18n.format("catalogue.gui.ahead", update.latestFound()); + case BETA_OUTDATED: + if (update.homepage() != null) { + return TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available", update.latestFound(), update.homepage()); + } else { + return TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available_no_page", update.latestFound()); + } + case OUTDATED: + if (update.homepage() != null) { + return TextFormatting.GREEN + I18n.format("catalogue.gui.update_available", update.latestFound(), update.homepage()); + } else { + return TextFormatting.GREEN + I18n.format("catalogue.gui.update_available_no_page", update.latestFound()); + } + } + return ""; + } + + @Override + public @Nullable IResourcePack getResourcePack() { + return FMLClientHandler.instance().getResourcePackFor(this.getModId()); + } + + private @Nullable ModMetadata getMetadata() { + ModMetadata metadata = this.info.getMetadata(); + return metadata != null && !metadata.autogenerated ? metadata : null; + } + + private Type analyzeType(ModContainer info) { + if (LIB_MODS.contains(info.getModId())) { + return Type.LIBRARY; + } else { + return Type.DEFAULT; + } + } + + private Set analyzeDependencies(ModContainer source) { + List versions = source.getDependencies(); + return versions.stream() + .map(ArtifactVersion::getLabel) + .filter(modid -> !IGNORED_DEPENDENCIES.contains(modid)) + .collect(Collectors.toUnmodifiableSet()); + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java index f747472ba..9a551a009 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java @@ -2,6 +2,9 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import org.lwjgl.opengl.GL11; /** @@ -10,6 +13,7 @@ public class ClientHelper { /** * Creates a scissor test using minecraft screen coordinates instead of pixel coordinates. + * * @param screenX * @param screenY * @param boxWidth @@ -36,4 +40,104 @@ public static boolean isMouseWithin(int x, int y, int width, int height, int mou public static boolean isPlayingGame() { return Minecraft.getMinecraft().player != null; } + + public static void blitNineSlicedSprite(NineSlice nineSlice, int x, int y, int width, int height) { + blitNineSlicedSprite(nineSlice, x, y, 0, width, height); + } + + public static void blitNineSlicedSprite(NineSlice nineSlice, int x, int y, int blitOffset, int width, int height) { + NineSlice.Border border = nineSlice.border(); + int i = Math.min(border.left(), width / 2); + int j = Math.min(border.right(), width / 2); + int k = Math.min(border.top(), height / 2); + int l = Math.min(border.bottom(), height / 2); + if (width == nineSlice.width() && height == nineSlice.height()) { + blitSprite(nineSlice.width(), nineSlice.height(), 0, 0, x, y, blitOffset, width, height); + } else if (height == nineSlice.height()) { + blitSprite(nineSlice.width(), nineSlice.height(), 0, 0, x, y, blitOffset, i, height); + blitTiledSprite(x + i, y, blitOffset, width - j - i, height, i, 0, nineSlice.width() - j - i, nineSlice.height(), nineSlice.width(), nineSlice.height()); + blitSprite(nineSlice.width(), nineSlice.height(), nineSlice.width() - j, 0, x + width - j, y, blitOffset, j, height); + } else if (width == nineSlice.width()) { + blitSprite(nineSlice.width(), nineSlice.height(), 0, 0, x, y, blitOffset, width, k); + blitTiledSprite(x, y + k, blitOffset, width, height - l - k, 0, k, nineSlice.width(), nineSlice.height() - l - k, nineSlice.width(), nineSlice.height()); + blitSprite(nineSlice.width(), nineSlice.height(), 0, nineSlice.height() - l, x, y + height - l, blitOffset, width, l); + } else { + blitSprite(nineSlice.width(), nineSlice.height(), 0, 0, x, y, blitOffset, i, k); + blitTiledSprite(x + i, y, blitOffset, width - j - i, k, i, 0, nineSlice.width() - j - i, k, nineSlice.width(), nineSlice.height()); + blitSprite(nineSlice.width(), nineSlice.height(), nineSlice.width() - j, 0, x + width - j, y, blitOffset, j, k); + blitSprite(nineSlice.width(), nineSlice.height(), 0, nineSlice.height() - l, x, y + height - l, blitOffset, i, l); + blitTiledSprite(x + i, y + height - l, blitOffset, width - j - i, l, i, nineSlice.height() - l, nineSlice.width() - j - i, l, nineSlice.width(), nineSlice.height()); + blitSprite(nineSlice.width(), nineSlice.height(), nineSlice.width() - j, nineSlice.height() - l, x + width - j, y + height - l, blitOffset, j, l); + blitTiledSprite(x, y + k, blitOffset, i, height - l - k, 0, k, i, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height()); + blitTiledSprite(x + i, y + k, blitOffset, width - j - i, height - l - k, i, k, nineSlice.width() - j - i, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height()); + blitTiledSprite(x + width - j, y + k, blitOffset, i, height - l - k, nineSlice.width() - j, k, j, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height()); + } + } + + private static void blitTiledSprite(int x, int y, int blitOffset, int width, int height, int uPosition, int vPosition, int spriteWidth, int spriteHeight, int nineSliceWidth, int nineSliceHeight) { + if (width > 0 && height > 0) { + if (spriteWidth <= 0 || spriteHeight <= 0) { + throw new IllegalArgumentException("Tiled sprite texture size must be positive, got " + spriteWidth + "x" + spriteHeight); + } + + for (int i = 0; i < width; i += spriteWidth) { + int j = Math.min(spriteWidth, width - i); + + for (int k = 0; k < height; k += spriteHeight) { + int l = Math.min(spriteHeight, height - k); + blitSprite(nineSliceWidth, nineSliceHeight, uPosition, vPosition, x + i, y + k, blitOffset, j, l); + } + } + } + } + + private static void blitSprite(int textureWidth, int textureHeight, int uPosition, int vPosition, int x, int y, int blitOffset, int uWidth, int vHeight) { + if (uWidth != 0 && vHeight != 0) { + float minU = (float) uPosition / (float) textureWidth; + float maxU = (float) (uPosition + uWidth) / (float) textureWidth; + float minV = (float) vPosition / (float) textureHeight; + float maxV = (float) (vPosition + vHeight) / (float) textureHeight; + + innerBlit(x, x + uWidth, y, y + vHeight, blitOffset, minU, maxU, minV, maxV); + } + } + + /** + * Performs the inner blit operation for rendering a texture with the specified coordinates and texture coordinates without color tinting. + * + * @param x1 the x-coordinate of the first corner of the blit position. + * @param x2 the x-coordinate of the second corner of the blit position + * . + * @param y1 the y-coordinate of the first corner of the blit position. + * @param y2 the y-coordinate of the second corner of the blit position + * . + * @param blitOffset the z-level offset for rendering order. + * @param minU the minimum horizontal texture coordinate. + * @param maxU the maximum horizontal texture coordinate. + * @param minV the minimum vertical texture coordinate. + * @param maxV the maximum vertical texture coordinate. + */ + static void innerBlit(int x1, int x2, int y1, int y2, int blitOffset, float minU, float maxU, float minV, float maxV) { + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuffer(); + bufferbuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + bufferbuilder.pos(x1, y1, blitOffset).tex(minU, minV).endVertex(); + bufferbuilder.pos(x1, y2, blitOffset).tex(minU, maxV).endVertex(); + bufferbuilder.pos(x2, y2, blitOffset).tex(maxU, maxV).endVertex(); + bufferbuilder.pos(x2, y1, blitOffset).tex(maxU, minV).endVertex(); + tessellator.draw(); + } + + // Info of the texture + public record NineSlice(int width, int height, Border border) { + public record Border(int left, int top, int right, int bottom) { + public Border(int border) { + this(border, border, border, border); + } + } + + public NineSlice(int width, int height, int border) { + this(width, height, new Border(border)); + } + } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/IModData.java b/src/main/java/com/cleanroommc/catalogue/client/IModData.java new file mode 100644 index 000000000..6f4dc7906 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/IModData.java @@ -0,0 +1,97 @@ +package com.cleanroommc.catalogue.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.IResourcePack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.TextFormatting; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +/** + * Author: MrCrayfish + */ +public interface IModData { + Type getType(); + + String getModId(); + + String getDisplayName(); + + @NotNull + String getVersion(); + + @NotNull + String getInnerVersion(); + + @NotNull + String getDescription(); + + @NotNull + String getItemIcon(); + + @NotNull + String getImageIcon(); + + @NotNull + String getLicense(); + + @NotNull + String getCredits(); + + @NotNull + String getAuthors(); + + @NotNull + String getHomepage(); + + @NotNull + String getIssueTracker(); + + @NotNull + String getBanner(); + + @NotNull + String getBackground(); + + @Nullable + Update getUpdate(); + + @Nullable + IResourcePack getResourcePack(); + + Set getDependencies(); //TODO lazily + + boolean hasConfig(); + + boolean isLibrary(); + + void openConfigScreen(Minecraft minecraft, GuiScreen parent); + + void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y); + + @NotNull + String getUpdateText(Update update); + + record Update(boolean animated, String url, int texOffset, ResourceLocation textures, boolean updatable, + String latestFound, String homepage) { + } + + enum Type { + DEFAULT(TextFormatting.RESET), + LIBRARY(TextFormatting.DARK_GRAY), + GENERATED(TextFormatting.AQUA); + + private final TextFormatting style; + + Type(TextFormatting style) { + this.style = style; + } + + public TextFormatting getStyle() { + return this.style; + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java b/src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java new file mode 100644 index 000000000..df746526e --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java @@ -0,0 +1,9 @@ +package com.cleanroommc.catalogue.client; + +import net.minecraft.util.ResourceLocation; + +/** + * Author: MrCrayfish + */ +public record ImageInfo(ResourceLocation resource, int width, int height, Runnable unregister) { +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java b/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java new file mode 100644 index 000000000..5aeea63ce --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java @@ -0,0 +1,31 @@ +package com.cleanroommc.catalogue.client; + +import com.cleanroommc.catalogue.exception.InvalidBrandingImageException; +import org.apache.commons.lang3.function.TriFunction; + +import java.awt.image.BufferedImage; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; + +/** + * Author: MrCrayfish + */ +public record ImagePredicate(TriFunction predicate, + BiFunction errorMessage) implements BiPredicate { + public static final ImagePredicate SQUARE = new ImagePredicate((image, maxWidth, maxHeight) -> Objects.equals(image.getWidth(), image.getHeight()), (maxWidth, maxHeight) -> "Image must be a square"); + public static final ImagePredicate EQUAL = new ImagePredicate((image, maxWidth, maxHeight) -> image.getWidth() == maxWidth && image.getHeight() == maxHeight, "Image dimensions must be exactly %spx by %spx"::formatted); + public static final ImagePredicate LESS_THAN_OR_EQUAL = new ImagePredicate((image, maxWidth, maxHeight) -> image.getWidth() <= maxWidth && image.getHeight() <= maxHeight, "Image dimensions must be less than or equal to %spx by %spx"::formatted); + public static final ImagePredicate ANY = new ImagePredicate((image, maxWidth, maxHeight) -> true, (maxWidth, maxHeight) -> ""); + + @Override + public boolean test(BufferedImage image, Branding branding) throws InvalidBrandingImageException { + if (image == null) { + throw new InvalidBrandingImageException("Image is null"); + } + if (!this.predicate.apply(image, branding.imageWidth(), branding.imageHeight())) { + throw new InvalidBrandingImageException(this.errorMessage.apply(branding.imageWidth(), branding.imageHeight())); + } + return true; + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 9568d75b6..90d472766 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -1,28 +1,25 @@ package com.cleanroommc.catalogue.client.screen; -import com.cleanroommc.catalogue.CatalogueConfig; import com.cleanroommc.catalogue.CatalogueConstants; +import com.cleanroommc.catalogue.Utils; +import com.cleanroommc.catalogue.client.Branding; import com.cleanroommc.catalogue.client.ClientHelper; -import com.cleanroommc.catalogue.client.screen.widget.CatalogueCheckBoxButton; -import com.cleanroommc.catalogue.client.screen.widget.CatalogueIconButton; -import com.cleanroommc.catalogue.client.screen.widget.CatalogueListExtended; -import com.cleanroommc.catalogue.client.screen.widget.CatalogueTextField; +import com.cleanroommc.catalogue.client.IModData; +import com.cleanroommc.catalogue.client.ImageInfo; +import com.cleanroommc.catalogue.client.screen.widget.*; +import com.cleanroommc.catalogue.platform.ClientServices; import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiOptions; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.RenderHelper; import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.texture.DynamicTexture; -import net.minecraft.client.renderer.texture.TextureManager; -import net.minecraft.client.renderer.texture.TextureUtil; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.resources.I18n; -import net.minecraft.client.resources.IResourcePack; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -31,61 +28,82 @@ import net.minecraft.util.text.TextComponentString; import net.minecraft.util.text.TextFormatting; import net.minecraft.util.text.event.ClickEvent; -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.client.FMLClientHandler; -import net.minecraftforge.fml.client.IModGuiFactory; -import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.common.ModContainer; -import net.minecraftforge.fml.common.ModMetadata; import net.minecraftforge.fml.common.registry.ForgeRegistries; +import org.apache.commons.lang3.mutable.MutableBoolean; +import org.apache.commons.lang3.mutable.MutableObject; import org.apache.commons.lang3.tuple.Pair; -import org.lwjgl.input.Keyboard; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL11; +import org.lwjglx.input.Keyboard; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.awt.Desktop; -import java.awt.image.BufferedImage; +import java.awt.*; import java.io.IOException; -import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.*; +import java.util.List; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.regex.Pattern; import java.util.stream.Collectors; -public class CatalogueModListScreen extends GuiScreen { - private static final Comparator SORT = Comparator.comparing(o -> o.getData().getName()); - private static final ResourceLocation MISSING_BANNER = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/missing_banner.png"); - private static final ResourceLocation MISSING_BACKGROUND = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/missing_background.png"); - private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); - private static final ResourceLocation MINECRAFT_LOGO = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/minecraft.png"); - private static final ImageInfo MISSING_BANNER_INFO = new ImageInfo(MISSING_BANNER, new Dimension(120, 120)); +public class CatalogueModListScreen extends GuiScreen implements DropdownMenuHandler { + private static final Favourites FAVOURITES = new Favourites(); + private static final Comparator SORT_ALPHABETICALLY = Comparator.comparing(o -> o.getData().getDisplayName()); + private static final Comparator SORT_ALPHABETICALLY_REVERSED = SORT_ALPHABETICALLY.reversed(); + private static final Comparator SORT_FAVOURITES_FIRST = Comparator.comparing(ModListEntry::getData, Comparator.comparing(data -> FAVOURITES.has(data.getModId()))).reversed().thenComparing(SORT_ALPHABETICALLY); + private static final MutableObject OPTION_QUERY = new MutableObject<>(""); + private static final MutableBoolean OPTION_HIDE_LIBRARIES = new MutableBoolean(true); + private static final MutableBoolean OPTION_CONFIGS_ONLY = new MutableBoolean(false); + private static final MutableBoolean OPTION_UPDATES_ONLY = new MutableBoolean(false); + private static final MutableBoolean OPTION_FAVOURITES_ONLY = new MutableBoolean(false); + private static final MutableObject> OPTION_SORT = new MutableObject<>(SORT_ALPHABETICALLY); + private static final ResourceLocation MISSING_BANNER = Utils.resource("textures/gui/missing_banner.png"); + private static final ResourceLocation MISSING_BACKGROUND = Utils.resource("textures/gui/missing_background.png"); + private static final ResourceLocation MINECRAFT_LOGO = Utils.resource("textures/gui/minecraft.png"); + private static final ImageInfo MISSING_BANNER_INFO = new ImageInfo(MISSING_BANNER, 120, 120, () -> { + }); private static final Map BANNER_CACHE = new HashMap<>(); private static final Map IMAGE_ICON_CACHE = new HashMap<>(); private static final Map ITEM_ICON_CACHE = new HashMap<>(); - private static final Map CACHED_MODS = new HashMap<>(); - private static final List LIB_MODS = Arrays.asList(CatalogueConfig.libraryList); + private static final Map CACHED_MODS = new HashMap<>(); + private static final Pattern MOD_ID_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{1,63}$"); private static final Supplier> COUNTS = Suppliers.memoize(() -> { int[] counts = new int[2]; - CACHED_MODS.forEach((modId, data) -> counts[LIB_MODS.contains(data.getModId()) ? 1 : 0]++); + CACHED_MODS.forEach((modId, data) -> counts[data.isLibrary() ? 1 : 0]++); return Pair.of(counts[0], counts[1]); }); - private static ResourceLocation cachedBackground; + private static final Map SEARCH_FILTERS = ImmutableMap.builder() + .put("dependencies", new SearchFilter((query, data) -> { + IModData target = CACHED_MODS.get(query.toLowerCase(Locale.ENGLISH)); + return target != null && target.getDependencies().contains(data.getModId()); + })) + .put("dependents", new SearchFilter((query, data) -> { + return data.getDependencies().contains(query.toLowerCase(Locale.ENGLISH)); + })).build(); + private static final TextFormatting SEARCH_FILTER_KEY = TextFormatting.GOLD; + private static final TextFormatting SEARCH_FILTER_VALUE = TextFormatting.WHITE; + private static ImageInfo cachedBackground; private static boolean loaded = false; - private static String lastSearch = ""; private final GuiScreen parentScreen; + private CatalogueTextButton optionsButton; private CatalogueTextField searchTextField; private ModList modList; - private ModContainer selectedModData; + private IModData selectedModData; private CatalogueIconButton modFolderButton; private CatalogueIconButton configButton; private CatalogueIconButton websiteButton; private CatalogueIconButton issueButton; - private CatalogueCheckBoxButton updatesButton; private List activeTooltip; private int tooltipYOffset; private StringList descriptionList; + private @Nullable DropdownMenu menu; private long lastClickTime; @@ -93,24 +111,51 @@ public CatalogueModListScreen(GuiScreen parent) { super(); this.parentScreen = parent; if (!loaded) { - Loader.instance().getActiveModList().forEach(data -> CACHED_MODS.put(data.getModId(), data)); - BANNER_CACHE.put("minecraft", new ImageInfo(MINECRAFT_LOGO, new Dimension(1024, 256))); + ClientServices.PLATFORM.getAllModData().forEach(data -> CACHED_MODS.put(data.getModId(), data)); + CACHED_MODS.put("minecraft", new MinecraftModData()); // Override minecraft + BANNER_CACHE.put("minecraft", new ImageInfo(MINECRAFT_LOGO, 1024, 256, () -> { + })); + FAVOURITES.load(); loaded = true; } } + @Override + public void setMenu(@Nullable DropdownMenu menu) { + if (this.menu != null && this.menu != menu) { + this.menu.hide(); + } + this.menu = menu; + } + @Override public void initGui() { super.initGui(); - this.searchTextField = new CatalogueTextField(0, this.fontRenderer, 11, 25, 148, 20); - this.searchTextField.setCanLoseFocus(true); - this.searchTextField.setEnableBackgroundDrawing(true); - this.searchTextField.setText(lastSearch); + this.searchTextField = new CatalogueTextField(0, this.fontRenderer, 11, 25, 148, 20) { + @Override + public int getWidth() { + if (this.getText().startsWith("@")) { + return super.getWidth() - 16; + } + return super.getWidth(); + } + }; + this.searchTextField.setFormatter(this::formatQuery); + this.searchTextField.setMaxStringLength(128); + this.searchTextField.setText(OPTION_QUERY.getValue()); + this.searchTextField.setResponder(s -> { + if (!OPTION_QUERY.getValue().equals(s)) { + OPTION_QUERY.setValue(s); + this.updateSearchFieldSuggestion(s); + this.modList.filterAndUpdateList(); + this.updateSelectedModList(); + } + }); this.modList = new ModList(); this.modList.setSlotXBoundsFromLeft(10); - this.buttonList.add(new GuiButton(1, 10, modList.bottom + 8, 127, 20, I18n.format("gui.back"))); + this.addButton(new CatalogueTextButton(1, 10, modList.bottom + 8, 127, 20, I18n.format("gui.back"))); this.modFolderButton = this.addButton(new CatalogueIconButton(2, 140, modList.bottom + 8, 0, 0)); int padding = 10; @@ -129,9 +174,9 @@ public void initGui() { this.descriptionList = new StringList(contentWidth + padding * 2, 50, contentLeft - padding, 130); - this.updatesButton = this.addButton(new CatalogueCheckBoxButton(6, this.modList.right - 14, 7, false)); + this.optionsButton = this.addButton(new CatalogueIconButton(6, this.modList.right - 16, 6, 40, 0, 16, 16)); - this.modList.filterAndUpdateList(this.searchTextField.getText()); + this.modList.filterAndUpdateList(); // Resizing window causes all widgets to be recreated, therefore need to update selected info if (this.selectedModData != null) { @@ -153,33 +198,61 @@ public void actionPerformed(GuiButton button) { break; case 2: try { - Desktop.getDesktop().open(Loader.instance().getConfigDir().getParentFile().toPath().resolve("mods").toFile()); + Desktop.getDesktop().open(ClientServices.PLATFORM.getModDirectory()); } catch (Exception e) { CatalogueConstants.LOG.error("Problem opening mods folder", e); } break; case 3: - if (!this.selectedModData.getModId().equals("minecraft")) { - try { - IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(this.selectedModData); - GuiScreen newScreen = guiFactory.createConfigGui(this); - this.mc.displayGuiScreen(newScreen); - } catch (Exception e) { - CatalogueConstants.LOG.error("There was a critical issue trying to build the config GUI for {}", this.selectedModData.getModId(), e); - } - } else { - this.mc.displayGuiScreen(new GuiOptions(this, this.mc.gameSettings)); + if (this.selectedModData != null) { + this.selectedModData.openConfigScreen(this.mc, this); } break; case 4: - this.openLink(0, this.selectedModData); + this.openLink(this.selectedModData.getHomepage()); break; case 5: - this.openLink(1, this.selectedModData); + this.openLink(this.selectedModData.getIssueTracker()); break; case 6: - this.modList.filterAndUpdateList(this.searchTextField.getText()); - this.updateSelectedModList(); + DropdownMenu menu = DropdownMenu.builder(this) + .setMinItemSize(100, 16) + .setAlignment(DropdownMenu.Alignment.BELOW_RIGHT) + .addMenu(I18n.format("catalogue.gui.filters"), DropdownMenu.builder(this) + .setMinItemSize(60, 16) + .setAlignment(DropdownMenu.Alignment.END_TOP) + .addCheckbox(I18n.format("catalogue.gui.filters.configs_only"), OPTION_CONFIGS_ONLY, newValue -> { + this.modList.filterAndUpdateList(); + return false; + }) + .addCheckbox(I18n.format("catalogue.gui.filters.updates_only"), OPTION_UPDATES_ONLY, newValue -> { + this.modList.filterAndUpdateList(); + return false; + }) + .addCheckbox(I18n.format("catalogue.gui.filters.favourites"), OPTION_FAVOURITES_ONLY, newValue -> { + this.modList.filterAndUpdateList(); + return false; + })) + .addMenu(I18n.format("catalogue.gui.sort"), DropdownMenu.builder(this) + .setMinItemSize(60, 16) + .setAlignment(DropdownMenu.Alignment.END_TOP) + .addItem(I18n.format("catalogue.gui.sort.alphabetically"), () -> { + OPTION_SORT.setValue(SORT_ALPHABETICALLY); + this.modList.filterAndUpdateList(); + }) + .addItem(I18n.format("catalogue.gui.sort.alphabetically_reverse"), () -> { + OPTION_SORT.setValue(SORT_ALPHABETICALLY_REVERSED); + this.modList.filterAndUpdateList(); + }) + .addItem(I18n.format("catalogue.gui.sort.favourites_first"), () -> { + OPTION_SORT.setValue(SORT_FAVOURITES_FIRST); + this.modList.filterAndUpdateList(); + })) + .addCheckbox(I18n.format("catalogue.gui.hide_libraries"), OPTION_HIDE_LIBRARIES, newValue -> { + this.modList.filterAndUpdateList(); + return false; + }).build(); + menu.toggle(button); break; } } @@ -187,27 +260,53 @@ public void actionPerformed(GuiButton button) { @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.activeTooltip = null; + + boolean inMenu = this.menu != null; this.drawDefaultBackground(); - this.drawModList(mouseX, mouseY, partialTicks); - this.drawModInfo(mouseX, mouseY, partialTicks); - super.drawScreen(mouseX, mouseY, partialTicks); + int disableableMouseX = inMenu ? -1000 : mouseX; + int disableableMouseY = inMenu ? -1000 : mouseY; + this.drawModList(disableableMouseX, disableableMouseY, partialTicks); + this.drawModInfo(disableableMouseX, disableableMouseY, partialTicks); + super.drawScreen(disableableMouseX, disableableMouseY, partialTicks); + + if (OPTION_QUERY.getValue().startsWith("@")) { + int iconX = this.searchTextField.x + this.searchTextField.width - 15; + int iconY = this.searchTextField.y + (this.searchTextField.height - 10) / 2; + this.mc.getTextureManager().bindTexture(CatalogueIconButton.TEXTURE); + drawModalRectWithCustomSizedTexture(iconX, iconY, 20, 10, 10, 10, 64, 64); + + if (this.menu == null && ClientHelper.isMouseWithin(iconX, iconY, 10, 10, mouseX, mouseY)) { + this.setActiveTooltip(I18n.format("catalogue.gui.advanced_search.info")); + } + } - Optional optional = Optional.ofNullable(CACHED_MODS.get(CatalogueConstants.MOD_ID)); + Optional optional = Optional.ofNullable(CACHED_MODS.get(CatalogueConstants.MOD_ID)); optional.ifPresent(this::loadAndCacheLogo); - ImageInfo imageInfo = BANNER_CACHE.get(CatalogueConstants.MOD_ID); - if (imageInfo != null) { - Dimension size = imageInfo.size(); - this.mc.getTextureManager().bindTexture(imageInfo.resource()); - drawScaledCustomSizeModalRect(10, 9, 0.0F, 0.0F, size.width, size.height, 10, 10, size.width, size.height); + ImageInfo bannerInfo = BANNER_CACHE.get(CatalogueConstants.MOD_ID); + if (bannerInfo != null) { + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + this.mc.getTextureManager().bindTexture(bannerInfo.resource()); + drawScaledCustomSizeModalRect(10, 9, 0.0F, 0.0F, bannerInfo.width(), bannerInfo.height(), 10, 10, bannerInfo.width(), bannerInfo.height()); + GlStateManager.disableBlend(); } - if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("catalogue.gui.info")); - this.tooltipYOffset = 10; - } + if (this.menu != null) { + this.menu.drawScreen(this.mc, mouseX, mouseY, partialTicks); + } else { + if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { + this.setActiveTooltip(I18n.format("catalogue.gui.info")); + this.tooltipYOffset = 10; + } + + if (this.optionsButton.isMouseOver()) { + this.setActiveTooltip(I18n.format("catalogue.gui.options")); + this.tooltipYOffset = 10; + } - if (this.modFolderButton.isMouseOver()) { - this.setActiveTooltip(I18n.format("catalogue.gui.open_mods_folder")); + if (this.modFolderButton.isMouseOver()) { + this.setActiveTooltip(I18n.format("catalogue.gui.open_mods_folder")); + } } if (this.activeTooltip != null) { @@ -225,6 +324,17 @@ public void handleMouseInput() throws IOException { @Override protected void mouseClicked(int mouseX, int mouseY, int button) throws IOException { + // Menu widget + if (this.menu != null) { + if (!this.menu.mousePressed(this.mc, mouseX, mouseY)) { + this.setMenu(null); + } + return; + } + + // Mod List + if (this.modList.mouseClicked(mouseX, mouseY, button)) return; + // Catalogue button if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY) && button == 0) { this.openLink("https://www.curseforge.com/minecraft/mc-mods/catalogue"); @@ -234,13 +344,12 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti // Version check button if (this.selectedModData != null) { int contentLeft = this.modList.right + 12 + 10; - String version = I18n.format("catalogue.gui.version", this.selectedModData.getDisplayVersion()); + String version = this.selectedModData.getVersion(); int versionWidth = this.fontRenderer.getStringWidth(version); if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { - ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); - if (shouldUpdate(update) && update.homepage != null) { - this.openLink(update.homepage); - return; + IModData.Update update = this.selectedModData.getUpdate(); + if (update != null && update.homepage() != null && !update.homepage().isBlank() && update.updatable()) { + this.openLink(update.homepage()); } } } @@ -251,9 +360,6 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti // Right click to empty if (button == 1) { this.searchTextField.setText(""); - this.updateSearchFieldSuggestion(""); - this.modList.filterAndUpdateList(""); - lastSearch = ""; return; } // Left click to apply suggestions @@ -263,9 +369,6 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti if (!text.isEmpty() && currentTine - this.lastClickTime < 250L && !this.searchTextField.getIsTextTruncated()) { text += this.searchTextField.getSuggestion(); this.searchTextField.setText(text); - this.updateSearchFieldSuggestion(text); - this.modList.filterAndUpdateList(text); - lastSearch = text; this.lastClickTime = currentTine; return; } @@ -282,14 +385,7 @@ protected void keyTyped(char typedChar, int key) throws IOException { this.searchTextField.setFocused(true); return; } - if (this.searchTextField.textboxKeyTyped(typedChar, key)) { - String s = this.searchTextField.getText(); - this.updateSearchFieldSuggestion(s); - this.modList.filterAndUpdateList(s); - this.updateSelectedModList(); - lastSearch = s; - return; - } + if (this.searchTextField.textboxKeyTyped(typedChar, key)) return; super.keyTyped(typedChar, key); } @@ -307,16 +403,10 @@ public void updateScreen() { * @param partialTicks the partial ticks */ private void drawModList(int mouseX, int mouseY, float partialTicks) { - GlStateManager.enableBlend(); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - this.mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); - drawModalRectWithCustomSizedTexture(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); - GlStateManager.disableBlend(); - this.modList.drawScreen(mouseX, mouseY, partialTicks); this.searchTextField.drawTextBox(); - String modsLabel = TextFormatting.BOLD + I18n.format("catalogue.gui.title"); + String modsLabel = TextFormatting.BOLD + I18n.format("catalogue.gui.mod_list"); String countLabel = TextFormatting.GRAY + "(" + CACHED_MODS.size() + ")"; String title = modsLabel + " " + countLabel; int titleWidth = this.fontRenderer.getStringWidth(title); @@ -333,16 +423,41 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { this.setActiveTooltip(lines); this.tooltipYOffset = 10; } - - if (ClientHelper.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { - this.setActiveTooltip(I18n.format("catalogue.gui.filter_updates")); - this.tooltipYOffset = 10; - } } private class ModList extends CatalogueListExtended { - private List entries = Lists.newArrayList(); - private int selectedIndex = -1; + private static final Predicate SEARCH_PREDICATE = data -> { + String query = OPTION_QUERY.getValue(); + if (query.startsWith("@")) { + return performSearchFilter(query, data); + } + return data.getDisplayName() + .toLowerCase(Locale.ENGLISH) + .contains(query.toLowerCase(Locale.ENGLISH)); + }; + private static final Predicate FILTER_PREDICATE = data -> { + // We ignore filters when using special query + String query = OPTION_QUERY.getValue(); + if (query.startsWith("@")) { + return true; + } + if (OPTION_CONFIGS_ONLY.booleanValue() && !data.hasConfig()) { + return false; + } + if (OPTION_UPDATES_ONLY.booleanValue() && (data.getUpdate() == null || data.getUpdate().updatable())) { + return false; + } + if (OPTION_HIDE_LIBRARIES.booleanValue() && data.isLibrary()) { + return false; + } + if (OPTION_FAVOURITES_ONLY.booleanValue() && !FAVOURITES.has(data.getModId())) { + return false; + } + return true; + }; + private boolean hideFavourites; + private List children = Lists.newArrayList(); + private ModListEntry selected; public ModList() { super(CatalogueModListScreen.this.mc, 150, CatalogueModListScreen.this.height, 46, CatalogueModListScreen.this.height - 35, 26); @@ -354,7 +469,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { super.drawScreen(mouseX, mouseY, partialTicks); GL11.glDisable(GL11.GL_SCISSOR_TEST); - if (this.entries.isEmpty()) { + if (this.children.isEmpty()) { String text = I18n.format("catalogue.gui.no_mods"); int left = this.left + this.width / 2; int top = this.top + (this.bottom - this.top - CatalogueModListScreen.this.fontRenderer.FONT_HEIGHT) / 2; @@ -362,48 +477,38 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { } } - @Override - protected void elementClicked(int slotIndex, boolean isDoubleClick, int mouseX, int mouseY) { - selectedIndex = slotIndex; - ModListEntry entry = entries.get(slotIndex); - CatalogueModListScreen.this.setSelectedModData(entry.data); - } - - public void filterAndUpdateList(String text) { + public void filterAndUpdateList() { List entries = CACHED_MODS.values().stream() - .filter(data -> data.getName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) - .filter(data -> !updatesButton.selected() || shouldUpdate(ForgeVersion.getCleanResult(data))) + .filter(SEARCH_PREDICATE) + .filter(FILTER_PREDICATE) .map(data -> new ModListEntry(data, this)) - .sorted(SORT) + .sorted(OPTION_SORT.getValue()) .collect(Collectors.toList()); - this.entries = entries; - this.selectMod(this.getEntryFromInfo(selectedModData)); - this.setAmountScrolled(0); + this.replaceEntries(entries); + this.clampAmountScrolled(); } - public ModListEntry getEntryFromInfo(ModContainer data) { - return this.entries.stream().filter(entry -> entry.data == data).findFirst().orElse(null); + public ModListEntry getEntryFromInfo(IModData data) { + return this.children.stream().filter(entry -> entry.data == data).findFirst().orElse(null); } - public void selectMod(int selectedIndex) { - this.selectedIndex = selectedIndex; + protected void centerScrollOn(ModListEntry pEntry) { + this.setAmountScrolled((float) (this.children.indexOf(pEntry) * this.slotHeight + this.slotHeight / 2 - (this.bottom - this.top) / 2)); } - public void selectMod(IGuiListEntry entry) { - this.selectMod(this.getEntryIndex(entry)); + protected void clearEntries() { + this.children.clear(); + this.selected = null; } - public void centerScrollOn(IGuiListEntry entry) { - this.setAmountScrolled(this.slotHeight * this.getEntryIndex(entry)); + protected void replaceEntries(Collection entries) { + this.clearEntries(); + this.children.addAll(entries); } @Override public IGuiListEntry getListEntry(int index) { - return this.entries.get(index); - } - - public int getEntryIndex(IGuiListEntry entry) { - return this.entries.indexOf(entry); + return this.children.get(index); } @Override @@ -428,16 +533,24 @@ public int getListWidth() { @Override protected int getSize() { - return this.entries.size(); + return this.children.size(); + } + + public ModListEntry getSelected() { + return this.selected; + } + + public void setSelected(@Nullable ModListEntry selected) { + this.selected = selected; } @Override protected boolean isSelected(int slotIndex) { - return this.selectedIndex == slotIndex; + return Objects.equals(this.getSelected(), this.children.get(slotIndex)); } @Override - protected void drawContainerBackground(@Nonnull Tessellator tessellator) { + protected void drawContainerBackground(@NotNull Tessellator tessellator) { if (ClientHelper.isPlayingGame()) { drawRect(this.left, this.top, this.right, this.bottom, 0x66000000); return; @@ -452,35 +565,118 @@ protected void drawTopAndBottom(Tessellator tessellator) { @Override protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { } + + @Override + public boolean mouseClicked(int mouseX, int mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) { + this.hideFavourites = button == 0 && mouseX >= this.getScrollBarX() && mouseY < this.getScrollBarX() + 6; + return true; + } + return false; + } + + @Override + public boolean mouseReleased(int mouseX, int mouseY, int button) { + if (this.hideFavourites && button == 0) { + this.hideFavourites = false; + } + return super.mouseReleased(mouseX, mouseY, button); + } + + public boolean isMouseOver() { + return ClientHelper.isMouseWithin(this.getListLeft(), this.top, this.width, this.bottom - this.top, this.mouseX, this.mouseY); + } + + public boolean shouldHideFavourites() { + return this.hideFavourites; + } + } + + private static boolean performSearchFilter(String query, IModData data) { + if (!query.startsWith("@")) + return false; + + int end = query.indexOf(":"); + if (end == -1) + return false; + + String type = query.substring(1, end).toLowerCase(Locale.ENGLISH); + if (!SEARCH_FILTERS.containsKey(type)) + return false; + + String value = query.substring(end + 1); + return SEARCH_FILTERS.get(type).predicate().test(value, data); + } + + private String formatQuery(String partial, int displayPos) { + String query = OPTION_QUERY.getValue(); + if (!query.startsWith("@")) { + return partial; + } + + int split = query.indexOf(":"); + if (split == -1) { + return SEARCH_FILTER_KEY + partial + TextFormatting.RESET; + } + + if (displayPos > split) { + return SEARCH_FILTER_VALUE + partial + TextFormatting.RESET; + } + + if (displayPos + partial.length() < split) { + return SEARCH_FILTER_KEY + partial + TextFormatting.RESET; + } + + split = partial.indexOf(":"); + if (split == -1) { + return SEARCH_FILTER_KEY + partial + TextFormatting.RESET; + } + + return SEARCH_FILTER_KEY + partial.substring(0, split + 1) + + SEARCH_FILTER_VALUE + partial.substring(split + 1) + TextFormatting.RESET; } private class ModListEntry implements CatalogueListExtended.IGuiListEntry { - private final ModContainer data; + private final IModData data; private final ModList list; + private final PinnedButton button; private ItemStack icon; - public ModListEntry(ModContainer data, ModList list) { + public ModListEntry(IModData data, ModList list) { this.data = data; this.list = list; + this.button = new PinnedButton(data.getModId()); this.icon = this.getItemIcon(); } @Override public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { // Draws mod name and version - drawString(fontRenderer, this.getFormattedModName(), left + 24, top + 2, 0xFFFFFF); - drawString(fontRenderer, TextFormatting.GRAY + this.data.getDisplayVersion(), left + 24, top + 12, 0xFFFFFF); + boolean inOptionsMenu = CatalogueModListScreen.this.menu != null; + boolean drawFavouriteIcon = !inOptionsMenu && !this.list.shouldHideFavourites() && ClientHelper.isMouseWithin(left + rowWidth - rowHeight - 4, top, rowHeight + 4, rowHeight, mouseX, mouseY) || FAVOURITES.has(this.data.getModId()); + drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModName(drawFavouriteIcon), left + 24, top + 2, 0xFFFFFF); + drawString(CatalogueModListScreen.this.fontRenderer, TextFormatting.GRAY + this.data.getVersion(), left + 24, top + 12, 0xFFFFFF); // Draw image icon or fallback to item icon this.drawIcon(top, left); // Draws an icon if there is an update for the mod - ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.data); - if (shouldDraw(update)) { - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); - int vOffset = update.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; - drawModalRectWithCustomSizedTexture(left + rowWidth - 8 - 10, top + 6, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); + IModData.Update update = this.data.getUpdate(); + if (update != null) { + int iconLeft = left + rowWidth - 8 - 9 + (drawFavouriteIcon ? -14 : 0); + this.data.drawUpdateIcon(mc, update, iconLeft, top + 7); + } + + if (drawFavouriteIcon) { + this.button.x = left + rowWidth - this.button.width - 8; + this.button.y = top + (rowHeight - this.button.height) / 2; + this.button.drawButton(CatalogueModListScreen.this.mc, mouseX, mouseY, partialTicks); + if (!inOptionsMenu && this.button.isMouseOver()) { + String label = !FAVOURITES.has(this.data.getModId()) ? + I18n.format("catalogue.gui.favourite") : + I18n.format("catalogue.gui.remove_favourite"); + CatalogueModListScreen.this.setActiveTooltip(label); + } } } @@ -491,9 +687,8 @@ private void drawIcon(int top, int left) { if (iconInfo != null) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); - Dimension size = iconInfo.size(); mc.getTextureManager().bindTexture(iconInfo.resource()); - drawScaledCustomSizeModalRect(left + 4, top + 3, 0.0F, 0.0F, size.width, size.height, 16, 16, size.width, size.height); + drawScaledCustomSizeModalRect(left + 4, top + 3, 0.0F, 0.0F, iconInfo.width(), iconInfo.height(), 16, 16, iconInfo.width(), iconInfo.height()); GlStateManager.disableBlend(); return; } @@ -530,23 +725,20 @@ private ItemStack getItemIcon() { } // Gets the raw item icon resource string - ModMetadata metadata = this.data.getMetadata(); - if (metadata != null && !metadata.autogenerated) { - String itemIcon = metadata.iconItem; - if (!itemIcon.isBlank()) { - try { - // 0:mod id 1:item name (2:metadata) - String[] parts = itemIcon.split(":"); - Item item = Item.getByNameOrId(parts[0] + ":" + parts[1]); - if (item != null) { - int meta = parts.length > 2 ? Integer.parseInt(parts[2]) : 0; - ItemStack itemStack = new ItemStack(item, 1, meta); - ITEM_ICON_CACHE.put(this.data.getModId(), itemStack); - return itemStack; - } - } catch (Exception e) { - CatalogueConstants.LOG.debug("Failed to get customized item icon for mod '{}'", this.data.getModId(), e); + String itemIcon = data.getItemIcon(); + if (!itemIcon.isBlank()) { + try { + // 0:mod id 1:item name (2:metadata) + String[] parts = itemIcon.split(":"); + Item item = Item.getByNameOrId(parts[0] + ":" + parts[1]); + if (item != null) { + int meta = parts.length > 2 ? Integer.parseInt(parts[2]) : 0; + ItemStack itemStack = new ItemStack(item, 1, meta); + ITEM_ICON_CACHE.put(this.data.getModId(), itemStack); + return itemStack; } + } catch (Exception e) { + CatalogueConstants.LOG.debug("Failed to get customized item icon for mod '{}'", this.data.getModId(), e); } } @@ -574,25 +766,53 @@ private ItemStack getItemIcon() { return new ItemStack(Blocks.GRASS); } - private String getFormattedModName() { - String name = this.data.getName(); - int width = this.list.getListWidth() - (this.list.getMaxScroll() > 0 ? 30 : 24); - if (CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > width) { - name = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(name, width - 10) + "..."; + private String getFormattedModName(boolean favouriteIconVisible) { + String name = this.data.getDisplayName(); + int paddingEnd = 4; + int trimWidth = this.list.getListWidth() - 24 - paddingEnd; + IModData.Update update = this.data.getUpdate(); + if (update != null) { + trimWidth -= 12; + } + if (favouriteIconVisible) { + trimWidth -= 18; + } + if (CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > trimWidth) { + name = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(name, trimWidth - 8).trim() + "..."; } - if (LIB_MODS.contains(this.data.getModId())) { + if (this.data.isLibrary()) { return TextFormatting.DARK_GRAY + name; } return name; } - @Override - public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { - return true; + public IModData getData() { + return this.data; } - public ModContainer getData() { - return this.data; + @Override + public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { + if (this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) return false; + if (mouseEvent == 1) { + DropdownMenu menu = DropdownMenu.builder(CatalogueModListScreen.this) + .setMinItemSize(0, 16) + .setAlignment(DropdownMenu.Alignment.BELOW_LEFT) + .addItem(I18n.format("catalogue.gui.show_dependencies"), () -> { + String filter = "@dependencies:" + this.data.getModId(); + CatalogueModListScreen.this.searchTextField.setText(filter); + }) + .addItem(I18n.format("catalogue.gui.show_dependents"), () -> { + String filter = "@dependents:" + this.data.getModId(); + CatalogueModListScreen.this.searchTextField.setText(filter); + }).build(); + menu.toggle(mouseX, mouseY); + return false; + } else if (mouseEvent == 0) { + CatalogueModListScreen.this.setSelectedModData(this.data); + this.list.setSelected(this); + return true; + } + return false; } @Override @@ -602,6 +822,43 @@ public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relat @Override public void updatePosition(int slotIndex, int x, int y, float partialTicks) { } + + private class PinnedButton extends GuiButton { + private static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/icons.png"); + + private final String modId; + + public PinnedButton(String modId) { + super(0, 0, 0, 10, 10, ""); + this.modId = modId; + } + + @Override + public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTick) { + if (!this.visible) return; + this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + this.mouseDragged(mc, mouseX, mouseY); + int textureU = FAVOURITES.has(this.modId) ? 10 : 0; + mc.getTextureManager().bindTexture(TEXTURE); + drawModalRectWithCustomSizedTexture(this.x, this.y, textureU, 10, 10, 10, 64, 64); + } + + @Override + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { + if (super.mousePressed(mc, mouseX, mouseY)) { + FAVOURITES.toggle(this.modId); + ModListEntry.this.list.filterAndUpdateList(); + this.playPressSound(mc.getSoundHandler()); + return true; + } + return false; + } + + @Override + public boolean isMouseOver() { + return super.isMouseOver() && CatalogueModListScreen.this.modList.isMouseOver(); + } + } } /** @@ -612,24 +869,25 @@ public void updatePosition(int slotIndex, int x, int y, float partialTicks) { * @param partialTicks the partial ticks */ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { - this.drawVerticalLine(this.modList.right + 11, -1, this.height, 0xFF707070); - drawRect(this.modList.right + 12, 0, this.width, this.height, 0x66000000); + int listRight = this.modList.right; + this.drawVerticalLine(listRight + 11, -1, this.height, 0xFF707070); + drawRect(listRight + 12, 0, this.width, this.height, 0x66000000); this.descriptionList.drawScreen(mouseX, mouseY, partialTicks); - int contentLeft = this.modList.right + 12 + 10; + int contentLeft = listRight + 12 + 10; int contentWidth = this.width - contentLeft - 10; if (this.selectedModData != null) { - this.drawBackground(this.width - contentLeft + 10, this.modList.right + 12, 0); + this.drawBackground(this.width - contentLeft + 10, listRight + 12, 0); // Draw mod logo - this.drawBanner(contentWidth, contentLeft, 10, this.width - (this.modList.right + 12 + 10) - 10, 50); + this.drawBanner(contentWidth, contentLeft, 10, this.width - (listRight + 12 + 10) - 10, 50); // Draw mod name GlStateManager.pushMatrix(); GlStateManager.translate(contentLeft, 70, 0); GlStateManager.scale(2.0F, 2.0F, 2.0F); - drawString(this.fontRenderer, this.selectedModData.getName(), 0, 0, 0xFFFFFF); + drawString(this.fontRenderer, this.selectedModData.getDisplayName(), 0, 0, 0xFFFFFF); GlStateManager.popMatrix(); // Draw mod id @@ -638,76 +896,49 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); // Draw version - String displayVersion = this.selectedModData.getDisplayVersion(); + String displayVersion = this.selectedModData.getVersion(); this.drawStringWithLabel("catalogue.gui.version", displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); // Draw inner version if the display version is different from it int versionWidth = this.fontRenderer.getStringWidth(I18n.format("catalogue.gui.version", displayVersion)); - String innerVersion = this.selectedModData.getVersion(); + String innerVersion = this.selectedModData.getInnerVersion(); if (!displayVersion.equals(innerVersion) && ClientHelper.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { - this.setActiveTooltip(innerVersion); + this.setActiveTooltip(I18n.format("catalogue.gui.inner_version", innerVersion)); } // Draws an icon if there is an update for the mod - ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); - if (shouldDraw(update) && update.url != null) { - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - this.mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); - int vOffset = update.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; - drawModalRectWithCustomSizedTexture(contentLeft + versionWidth + 5, 92, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); + IModData.Update update = this.selectedModData.getUpdate(); + if (update != null && update.url() != null && !update.url().isBlank()) { + this.selectedModData.drawUpdateIcon(this.mc, update, contentLeft + versionWidth + 5, 92); if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { - switch (update.status) { - case BETA: - this.setActiveTooltip(TextFormatting.GOLD + I18n.format("catalogue.gui.beta")); - break; - case AHEAD: - this.setActiveTooltip(TextFormatting.LIGHT_PURPLE + I18n.format("catalogue.gui.ahead", update.latestFound)); - break; - case BETA_OUTDATED: - if (update.homepage != null) { - this.setActiveTooltip(TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available", update.latestFound, update.homepage)); - } else { - this.setActiveTooltip(TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available_no_page", update.latestFound)); - } - break; - case OUTDATED: - if (update.homepage != null) { - this.setActiveTooltip(TextFormatting.GREEN + I18n.format("catalogue.gui.update_available", update.latestFound, update.homepage)); - } else { - this.setActiveTooltip(TextFormatting.GREEN + I18n.format("catalogue.gui.update_available_no_page", update.latestFound)); - } - break; - } + String message = this.selectedModData.getUpdateText(update); + this.setActiveTooltip(message); } } - ModMetadata metadata = selectedModData.getMetadata(); - if (metadata != null && !metadata.autogenerated) { - - // Draw fade from the bottom - drawGradientRect(this.modList.right + 12, this.height - 50, this.width, this.height, 0x00000000, 0x66000000); + // Draw fade from the bottom + drawGradientRect(listRight + 12, this.height - 50, this.width, this.height, 0x00000000, 0x66000000); - int labelOffset = this.height - 18; + int labelOffset = this.height - 18; - // Draw license - String license = metadata.license; - if (!license.isBlank()) { - this.drawStringWithLabel("catalogue.gui.license", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); - labelOffset -= 15; - } + // Draw license + String license = this.selectedModData.getLicense(); + if (!license.isBlank()) { + this.drawStringWithLabel("catalogue.gui.licenses", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + labelOffset -= 15; + } - // Draw credits - String credits = metadata.credits; - if (!credits.isBlank()) { - this.drawStringWithLabel("catalogue.gui.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); - labelOffset -= 15; - } + // Draw credits + String credits = this.selectedModData.getCredits(); + if (!credits.isBlank()) { + this.drawStringWithLabel("catalogue.gui.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + labelOffset -= 15; + } - // Draw authors - String authors = metadata.getAuthorList(); - if (!authors.isBlank()) { - this.drawStringWithLabel("catalogue.gui.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); - } + // Draw authors + String authors = this.selectedModData.getAuthors(); + if (!authors.isBlank()) { + this.drawStringWithLabel("catalogue.gui.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); } } else { String message = TextFormatting.GRAY + I18n.format("catalogue.gui.no_selection"); @@ -716,7 +947,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { } private class StringList extends CatalogueListExtended { - private List entries = Lists.newArrayList(); + private final List entries = Lists.newArrayList(); public StringList(int width, int height, int left, int top) { super(CatalogueModListScreen.this.mc, width, height, top, top + height, 10); @@ -724,19 +955,16 @@ public StringList(int width, int height, int left, int top) { this.visible = false; } - public void setTextFromInfo(ModContainer data) { + public void setTextFromInfo(IModData data) { this.entries.clear(); this.visible = true; - if (data.getMetadata().description.trim().isBlank()) { + if (data.getDescription().trim().isBlank()) { this.visible = false; return; } - String description = data.getMetadata().description.trim(); - List lines = CatalogueModListScreen.this.fontRenderer.listFormattedStringToWidth(description, this.getListWidth()); - + List lines = CatalogueModListScreen.this.fontRenderer.listFormattedStringToWidth(data.getDescription().trim(), this.getListWidth()); for (String line : lines) { - String cleanLine = line.replace("\n", "").replace("\r", "").trim(); - this.entries.add(new StringEntry(cleanLine)); + this.entries.add(new StringEntry(line.replace("\n", "").replace("\r", "").trim())); } } @@ -774,8 +1002,8 @@ public int getListWidth() { } @Override - protected int getRowTop(int $$0) { - return super.getRowTop($$0) + 4; + protected int getRowTop(int pIndex) { + return super.getRowTop(pIndex) + 4; } @Override @@ -803,7 +1031,7 @@ protected void overlayBackground(int startY, int endY, int startAlpha, int endAl } private class StringEntry implements CatalogueListExtended.IGuiListEntry { - private String line; + private final String line; public StringEntry(String line) { this.line = line; @@ -844,8 +1072,10 @@ public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relat @SuppressWarnings("SameParameterValue") private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { String formatted = I18n.format(format, text); // Attempting to keep Forge's lang since it's already support many languages - String label = formatted.substring(0, formatted.indexOf(":") + 1); - String content = formatted.substring(formatted.indexOf(":") + 1); + String colon = ":"; + if (formatted.contains(":")) colon = ":"; + String label = formatted.substring(0, formatted.indexOf(colon) + 1); + String content = formatted.substring(formatted.indexOf(colon) + 1); if (this.fontRenderer.getStringWidth(formatted) > maxWidth) { content = this.fontRenderer.trimStringToWidth(content, maxWidth - this.fontRenderer.getStringWidth(label) - 7) + "..."; String credits = labelColor + label; @@ -859,145 +1089,90 @@ private void drawStringWithLabel(String format, String text, int x, int y, int m } } - private void loadAndCacheLogo(ModContainer data) { + private ImageInfo getBanner(String modId) { + // Try getting the banner for the mod + ImageInfo bannerInfo = BANNER_CACHE.get(modId); + if (bannerInfo != null) return bannerInfo; + + // Try using the icon image for the banner + ImageInfo iconInfo = IMAGE_ICON_CACHE.get(modId); + if (iconInfo != null) { + // Hack to make icon fill max banner height + int expandedWidth = iconInfo.width() * 10; + int expandedHeight = iconInfo.height() * 10; + return new ImageInfo(iconInfo.resource(), expandedWidth, expandedHeight, iconInfo.unregister()); + } + + // Fallback and just use missing banner + return MISSING_BANNER_INFO; + } + + private void loadAndCacheLogo(IModData data) { if (BANNER_CACHE.containsKey(data.getModId())) return; // Fills an empty logo as logo may not be present BANNER_CACHE.put(data.getModId(), null); - // Attempts to load the real logo - ModMetadata metadata = data.getMetadata(); - if (metadata == null) return; - String banner = metadata.logoFile; - if (banner.isBlank()) return; - - IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(data.getModId()); - BufferedImage image = null; - try { - if (resourcePack != null && !banner.startsWith("/")) { - image = resourcePack.getPackImage(); - } else { - if (!banner.startsWith("/")) { - banner = "/" + banner; - } - InputStream is = getClass().getResourceAsStream(banner); - if (is != null) image = TextureUtil.readBufferedImage(is); - } - if (image == null) return; - TextureManager manager = this.mc.getTextureManager(); - ResourceLocation resource = manager.getDynamicTextureLocation("modlogo", this.createLogoTexture(image, metadata.logoBlur)); - Dimension size = new Dimension(image.getWidth(), image.getHeight()); - BANNER_CACHE.put(data.getModId(), new ImageInfo(resource, size)); - } catch (IOException ignored) { - } + // Load the banner resource if present + Branding.BANNER.loadResource(data).ifPresent(info -> { + BANNER_CACHE.put(data.getModId(), info); + }); } - private void loadAndCacheIcon(ModContainer data) { + private void loadAndCacheIcon(IModData data) { if (IMAGE_ICON_CACHE.containsKey(data.getModId())) return; // Fills an empty icon as icon may not be present IMAGE_ICON_CACHE.put(data.getModId(), null); - ModMetadata metadata = data.getMetadata(); - if (metadata == null) return; - - // Attempts to load the real icon - String iconFile = metadata.iconFile; - if (!iconFile.isBlank()) { - if (!iconFile.startsWith("/")) { - iconFile = "/" + iconFile; - } - BufferedImage image = null; - try (InputStream is = getClass().getResourceAsStream(iconFile)) { - if (is != null) image = TextureUtil.readBufferedImage(is); - if (image != null) { - TextureManager manager = this.mc.getTextureManager(); - ResourceLocation resource = manager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(image, metadata.logoBlur)); - Dimension size = new Dimension(image.getWidth(), image.getHeight()); - IMAGE_ICON_CACHE.put(data.getModId(), new ImageInfo(resource, size)); - return; + // Load the icon branding + Branding.ICON.loadResource(data).ifPresentOrElse(info -> { + IMAGE_ICON_CACHE.put(data.getModId(), info); + }, () -> { + // If no icon, try and use the loaded banner if a square + ImageInfo bannerInfo = BANNER_CACHE.get(data.getModId()); + if (bannerInfo != null) { + if (bannerInfo.width() == bannerInfo.height()) { + IMAGE_ICON_CACHE.put(data.getModId(), bannerInfo); } - } catch (IOException ignored) { - } - } - - // Attempts to use the logo file if it's a square - String logoFile = metadata.logoFile; - if (!logoFile.isBlank()) { - IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(data.getModId()); - BufferedImage image = null; - try { - if (resourcePack != null && !logoFile.startsWith("/")) { - image = resourcePack.getPackImage(); - } else { - if (!logoFile.startsWith("/")) { - logoFile = "/" + logoFile; + } else { + // Otherwise temporarily load the banner, use if square, otherwise free the resource + Branding.BANNER.loadResource(data).ifPresent(info -> { + if (info.width() == info.height()) { + IMAGE_ICON_CACHE.put(data.getModId(), info); + BANNER_CACHE.put(data.getModId(), info); // Saves loading later + } else { + info.unregister().run(); } - InputStream is = getClass().getResourceAsStream(logoFile); - if (is != null) image = TextureUtil.readBufferedImage(is); - } - if (image == null || image.getWidth() != image.getHeight()) return; - - /* The first selected mod will have its logo cached before the icon, so we - * can just use the logo instead of loading the image again. */ - String modId = data.getModId(); - if (BANNER_CACHE.containsKey(modId)) { - IMAGE_ICON_CACHE.put(modId, BANNER_CACHE.get(modId)); - return; - } - - /* Since the icon will be same as the logo, we can cache into both icon and logo cache */ - TextureManager manager = this.mc.getTextureManager(); - DynamicTexture texture = this.createLogoTexture(image, metadata.logoBlur); - Dimension size = new Dimension(image.getWidth(), image.getHeight()); - ResourceLocation resource = manager.getDynamicTextureLocation("catalogueicon", texture); - IMAGE_ICON_CACHE.put(modId, new ImageInfo(resource, size)); - BANNER_CACHE.put(modId, new ImageInfo(resource, size)); - } catch (IOException ignored) { + }); } - } + }); } - private void loadAndCacheBackground(ModContainer data) { - // Deletes the last cached background since they are large images - if (cachedBackground != null) { - this.mc.getTextureManager().deleteTexture(cachedBackground); - } - cachedBackground = null; - - ModMetadata metadata = data.getMetadata(); - if (metadata == null) return; - String background = metadata.backgroundFile; - if (background.isBlank()) return; - - if (!background.startsWith("/")) { - background = "/" + background; - } - - BufferedImage image = null; - try (InputStream is = getClass().getResourceAsStream(background)) { - if (is != null) image = TextureUtil.readBufferedImage(is); - if (image == null || image.getWidth() != 512 || image.getHeight() != 256) return; - TextureManager textureManager = this.mc.getTextureManager(); - cachedBackground = textureManager.getDynamicTextureLocation("cataloguebackground", this.createLogoTexture(image, false)); - } catch (IOException ignored) { - } - } - - private DynamicTexture createLogoTexture(BufferedImage image, boolean smooth) { - return new DynamicTexture(image) { - @Override - public void updateDynamicTexture() { - TextureUtil.uploadTextureImageAllocate(this.getGlTextureId(), image, smooth, false); + private void reloadBackground(IModData data) { + Branding.BACKGROUND.loadResource(data).ifPresentOrElse(info -> { + cachedBackground = info; + }, () -> { + if (cachedBackground != null) { + cachedBackground.unregister().run(); + cachedBackground = null; } - }; + }); } + /** + * Draws the background that is visible when a mod is selected. Backgrounds are programmatically + * faded out to the bottom of the image. + * + * @param contentWidth the widget of the content area + * @param contentLeft the x position of the content area + * @param contentTop the y position of the content area + */ @SuppressWarnings("SameParameterValue") - private void drawBackground(int contentWidth, int x, int y) { + private void drawBackground(int contentWidth, int contentLeft, int contentTop) { if (this.selectedModData == null) return; - ResourceLocation texture = cachedBackground != null ? cachedBackground : MISSING_BACKGROUND; + ResourceLocation texture = cachedBackground != null ? cachedBackground.resource() : MISSING_BACKGROUND; this.mc.getTextureManager().bindTexture(texture); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); @@ -1008,10 +1183,10 @@ private void drawBackground(int contentWidth, int x, int y) { Tessellator tessellator = Tessellator.getInstance(); BufferBuilder builder = tessellator.getBuffer(); builder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); - builder.pos(x, y, this.zLevel).tex(0, 0).color(1.0F, 1.0F, 1.0F, 1.0F).endVertex(); - builder.pos(x, y + 128, this.zLevel).tex(0, 1).color(0.0F, 0.0F, 0.0F, 0.0F).endVertex(); - builder.pos(x + contentWidth, y + 128, this.zLevel).tex(1, 1).color(0.0F, 0.0F, 0.0F, 0.0F).endVertex(); - builder.pos(x + contentWidth, y, this.zLevel).tex(1, 0).color(1.0F, 1.0F, 1.0F, 1.0F).endVertex(); + builder.pos(contentLeft, contentTop, this.zLevel).tex(0, 0).color(1.0F, 1.0F, 1.0F, 1.0F).endVertex(); + builder.pos(contentLeft, contentTop + 128, this.zLevel).tex(0, 1).color(0.0F, 0.0F, 0.0F, 0.0F).endVertex(); + builder.pos(contentLeft + contentWidth, contentTop + 128, this.zLevel).tex(1, 1).color(0.0F, 0.0F, 0.0F, 0.0F).endVertex(); + builder.pos(contentLeft + contentWidth, contentTop, this.zLevel).tex(1, 0).color(1.0F, 1.0F, 1.0F, 1.0F).endVertex(); tessellator.draw(); GlStateManager.disableBlend(); @@ -1022,54 +1197,35 @@ private void drawBackground(int contentWidth, int x, int y) { @SuppressWarnings("SameParameterValue") private void drawBanner(int contentWidth, int x, int y, int maxWidth, int maxHeight) { if (this.selectedModData != null) { - ImageInfo bannerInfo = this.getBanner(this.selectedModData.getModId()); - Dimension size = bannerInfo.size(); - int width = size.width; - int height = size.height; - if (size.width > maxWidth) { - width = maxWidth; - height = (width * size.height) / size.width; + ImageInfo info = this.getBanner(this.selectedModData.getModId()); + int displayWidth = info.width(); + int displayHeight = info.height(); + if (info.width() > maxWidth) { + displayWidth = maxWidth; + displayHeight = (displayWidth * info.height()) / info.width(); } - if (height > maxHeight) { - height = maxHeight; - width = (height * size.width) / size.height; + if (displayHeight > maxHeight) { + displayHeight = maxHeight; + displayWidth = (displayHeight * info.width()) / info.height(); } - x += (contentWidth - width) / 2; - y += (maxHeight - height) / 2; + x += (contentWidth - displayWidth) / 2; + y += (maxHeight - displayHeight) / 2; // Fix for minecraft logo - if (bannerInfo.resource() == MINECRAFT_LOGO) { + if (info.resource() == MINECRAFT_LOGO) { y += 8; } GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); - this.mc.getTextureManager().bindTexture(bannerInfo.resource()); - drawScaledCustomSizeModalRect(x, y, 0.0F, 0.0F, size.width, size.height, width, height, size.width, size.height); + this.mc.getTextureManager().bindTexture(info.resource()); + drawScaledCustomSizeModalRect(x, y, 0.0F, 0.0F, info.width(), info.height(), displayWidth, displayHeight, info.width(), info.height()); GlStateManager.disableBlend(); } } - private ImageInfo getBanner(String modId) { - // Try getting the banner for the mod - ImageInfo bannerInfo = BANNER_CACHE.get(modId); - if (bannerInfo != null) return bannerInfo; - - // Try using the icon image for the banner - ImageInfo iconInfo = IMAGE_ICON_CACHE.get(modId); - if (iconInfo != null) { - // Hack to make icon fill max banner height - Dimension size = iconInfo.size(); - Dimension newSize = new Dimension(size.width * 10, size.height * 10); - return new ImageInfo(iconInfo.resource(), newSize); - } - - // Fallback and just use missing banner - return MISSING_BANNER_INFO; - } - private void setActiveTooltip(String content) { this.activeTooltip = this.fontRenderer.listFormattedStringToWidth(content, 200); this.tooltipYOffset = 0; @@ -1080,33 +1236,28 @@ private void setActiveTooltip(List activeTooltip) { this.tooltipYOffset = 0; } - private void setSelectedModData(ModContainer data) { + /** + * Sets the selected mod data. This handles loading the logo and background, updates the states + * of widgets, like the config button enable state (if the mod has a config), and the description + * test. + * + * @param data the mod data to set as selected + */ + private void setSelectedModData(IModData data) { this.selectedModData = data; this.loadAndCacheLogo(data); - this.loadAndCacheBackground(data); + this.reloadBackground(data); this.configButton.visible = true; this.websiteButton.visible = true; this.issueButton.visible = true; - if (!this.selectedModData.getModId().equals("minecraft")) { - this.configButton.enabled = false; - IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(data); - if (guiFactory != null) { - this.configButton.enabled = guiFactory.hasConfigGui(); - } - } else { - this.configButton.enabled = true; - } - - ModMetadata metadata = data.getMetadata(); - if (metadata != null && !metadata.autogenerated) { - this.websiteButton.enabled = !metadata.url.isBlank(); - this.issueButton.enabled = !metadata.issueTrackerUrl.isBlank(); - } + this.configButton.enabled = data.hasConfig(); + this.websiteButton.enabled = !data.getHomepage().isBlank(); + this.issueButton.enabled = !data.getIssueTracker().isBlank(); int contentLeft = this.modList.right + 12 + 10; int contentWidth = this.width - contentLeft - 10; - int labelCount = this.getLabelCount(data); + int labelCount = this.getFooterTextElementCount(data); this.descriptionList.setWidth(contentWidth); this.descriptionList.setHeight(this.height - 135 - labelCount * 15 - 9); this.descriptionList.setSlotXBoundsFromLeft(contentLeft); @@ -1114,34 +1265,60 @@ private void setSelectedModData(ModContainer data) { this.descriptionList.setAmountScrolled(0); } - private int getLabelCount(ModContainer selectedModInfo) { + /** + * Gets the count of the footer text elements. This is used to corrrectly set the height of + * the description widget. + * + * @param data the mod data + * @return the count of footer text elements + */ + private int getFooterTextElementCount(IModData data) { int count = 0; - ModMetadata metadata = selectedModInfo.getMetadata(); - if (metadata != null && !metadata.autogenerated) { - if (!metadata.license.isBlank()) count++; - if (!metadata.credits.isBlank()) count++; - if (!metadata.authorList.isEmpty()) count++; - } + if (!data.getLicense().isBlank()) count++; + if (!data.getCredits().isBlank()) count++; + if (!data.getAuthors().isBlank()) count++; return count; } + @Override + public void onGuiClosed() { + FAVOURITES.save(); + } + private void updateSelectedModList() { ModListEntry selectedEntry = this.modList.getEntryFromInfo(this.selectedModData); if (selectedEntry != null) { - this.modList.selectMod(selectedEntry); + this.modList.setSelected(selectedEntry); } } private void updateSearchFieldSuggestion(String value) { if (value.isEmpty()) { this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search") + "..."); + } else if (value.startsWith("@")) { + // Mark as special search + int end = value.indexOf(":"); + if (end != -1) { + String type = value.substring(1, end); + Optional optional = SEARCH_FILTERS.keySet().stream().filter(filter -> { + return filter.startsWith(type.toLowerCase(Locale.ENGLISH)); + }).min(Comparator.comparing(String::length)); + if (optional.isPresent()) { + int length = type.length(); + this.searchTextField.setSuggestion(optional.get().substring(length)); + } else { + this.searchTextField.setSuggestion(""); + } + } else { + this.searchTextField.setSuggestion(""); + } } else { - Optional optional = CACHED_MODS.values().stream().filter(data -> { - return data.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); - }).min(Comparator.comparing(ModContainer::getName)); + Optional optional = CACHED_MODS.values().stream().filter(data -> { + return data.getDisplayName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); + }).min(Comparator.comparing(IModData::getDisplayName)); if (optional.isPresent()) { int length = value.length(); - String displayName = optional.get().getName(); + String displayName = optional.get().getDisplayName(); this.searchTextField.setSuggestion(displayName.substring(length)); } else { this.searchTextField.setSuggestion(""); @@ -1150,45 +1327,81 @@ private void updateSearchFieldSuggestion(String value) { } /** - * Opens a link with a url defined in the mod's info + * Creates a confirmation screen to open a link + * + * @param url the url to open */ - private void openLink(int key, @Nullable ModContainer configurable) { - if (configurable != null) { - ModMetadata metadata = configurable.getMetadata(); - // The config button is only enabled when checked, so it is unnecessary to check again. - switch (key) { - case 0: - this.openLink(metadata.url); - break; - case 1: - this.openLink(metadata.issueTrackerUrl); - break; - } - } - } - - private void openLink(String url) { + private void openLink(@Nullable String url) { + if (url == null) return; Style style = new Style().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)); this.handleComponentClick(new TextComponentString("").setStyle(style)); } - private static boolean shouldDraw(ForgeVersion.CheckResult update) { - return update != null && update.status.shouldDraw(); - } - - private static boolean shouldUpdate(ForgeVersion.CheckResult update) { - if (update == null) return false; - ForgeVersion.Status status = update.status; - return status == ForgeVersion.Status.OUTDATED || status == ForgeVersion.Status.BETA_OUTDATED; - } - private static boolean isKeyComboCtrlF(int keyID) { return keyID == Keyboard.KEY_F && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown(); } - private record Dimension(int width, int height) { + private record SearchFilter(BiPredicate predicate) { } - private record ImageInfo(ResourceLocation resource, Dimension size) { + private static class Favourites { + private final Set mods = new HashSet<>(); + private boolean needsSave; + private Path file; + + public void toggle(String modId) { + if (!this.mods.remove(modId)) { + this.mods.add(modId); + } + this.needsSave = true; + } + + public boolean has(String modId) { + return this.mods.contains(modId); + } + + private void init() { + try { + Path configDir = ClientServices.PLATFORM.getConfigDirectory(); + Path file = configDir.resolve("catalogue_favourites.txt"); + if (!Files.exists(file)) { + Files.createFile(file); + } + this.file = file; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void load() { + try { + this.init(); + this.mods.clear(); + Predicate modIdRegex = MOD_ID_PATTERN.asMatchPredicate(); + Files.readAllLines(file).forEach(s -> { + if (modIdRegex.test(s) && ClientServices.PLATFORM.isModLoaded(s)) { + this.mods.add(s); + } + }); + // Save immediately to remove invalid lines + this.needsSave = true; + this.save(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void save() { + if (!this.needsSave) + return; + + try { + this.needsSave = false; + this.init(); + Files.write(this.file, this.mods, StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/DropdownMenuHandler.java b/src/main/java/com/cleanroommc/catalogue/client/screen/DropdownMenuHandler.java new file mode 100644 index 000000000..626ced855 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/DropdownMenuHandler.java @@ -0,0 +1,11 @@ +package com.cleanroommc.catalogue.client.screen; + +import com.cleanroommc.catalogue.client.screen.widget.DropdownMenu; +import org.jetbrains.annotations.Nullable; + +/** + * Author: MrCrayfish + */ +public interface DropdownMenuHandler { + void setMenu(@Nullable DropdownMenu menu); +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java new file mode 100644 index 000000000..06c3d985f --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java @@ -0,0 +1,133 @@ +package com.cleanroommc.catalogue.client.screen; + +import com.cleanroommc.catalogue.client.IModData; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiOptions; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.IResourcePack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.Set; + +/** + * Author: MrCrayfish + */ +public class MinecraftModData implements IModData { + @Override + public Type getType() { + return Type.LIBRARY; + } + + @Override + public String getModId() { + return "minecraft"; + } + + @Override + public String getDisplayName() { + return "Minecraft"; + } + + @Override + public @NotNull String getVersion() { + return "1.12.2"; + } + + @Override + public @NotNull String getInnerVersion() { + return this.getVersion(); + } + + @Override + public @NotNull String getDescription() { + // Description provided by minecraft.wiki (CC BY-NC-SA 3.0) + return "Minecraft is a 3D sandbox adventure game developed by Mojang Studios where players can interact with a fully customizable three-dimensional world made of blocks and entities. Its diverse gameplay options allow players to choose the way they play, creating countless possibilities."; + } + + @Override + public @NotNull String getItemIcon() { + return ""; + } + + @Override + public @NotNull String getImageIcon() { + return ""; + } + + @Override + public @NotNull String getLicense() { + return "All Rights Reserved"; + } + + @Override + public @NotNull String getCredits() { + return ""; + } + + @Override + public @NotNull String getAuthors() { + return "Mojang AB"; + } + + @Override + public @NotNull String getHomepage() { + return "https://www.minecraft.net"; + } + + @Override + public @NotNull String getIssueTracker() { + return "https://bugs.mojang.com/projects/MC/issues"; + } + + @Override + public @NotNull String getBanner() { + return ""; + } + + @Override + public @NotNull String getBackground() { + return ""; + } + + @Override + public @Nullable Update getUpdate() { + return null; + } + + @Override + public @Nullable IResourcePack getResourcePack() { + return null; + } + + @Override + public Set getDependencies() { + return Collections.emptySet(); + } + + @Override + public boolean hasConfig() { + return true; + } + + @Override + public boolean isLibrary() { + return true; + } + + @Override + public void openConfigScreen(Minecraft minecraft, GuiScreen parent) { + minecraft.displayGuiScreen(new GuiOptions(parent, minecraft.gameSettings)); + } + + @Override + public void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y) { + + } + + @Override + public @NotNull String getUpdateText(Update update) { + return ""; + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/AbstractLayout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/AbstractLayout.java new file mode 100644 index 000000000..17bd8839f --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/AbstractLayout.java @@ -0,0 +1,91 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +import com.cleanroommc.catalogue.Utils; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public abstract class AbstractLayout implements Layout { + private int x; + private int y; + protected int width; + protected int height; + + public AbstractLayout(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + @Override + public void setX(int x) { + this.visitChildren(p_265043_ -> { + int i = p_265043_.getX() + (x - this.getX()); + p_265043_.setX(i); + }); + this.x = x; + } + + @Override + public void setY(int y) { + this.visitChildren(p_265586_ -> { + int i = p_265586_.getY() + (y - this.getY()); + p_265586_.setY(i); + }); + this.y = y; + } + + @Override + public int getX() { + return this.x; + } + + @Override + public int getY() { + return this.y; + } + + @Override + public int getWidth() { + return this.width; + } + + @Override + public int getHeight() { + return this.height; + } + + @SideOnly(Side.CLIENT) + protected abstract static class AbstractChildWrapper { + public final LayoutElement child; + public final LayoutSettings.LayoutSettingsImpl layoutSettings; + + protected AbstractChildWrapper(LayoutElement child, LayoutSettings layoutSettings) { + this.child = child; + this.layoutSettings = layoutSettings.getExposed(); + } + + public int getHeight() { + return this.child.getHeight() + this.layoutSettings.paddingTop + this.layoutSettings.paddingBottom; + } + + public int getWidth() { + return this.child.getWidth() + this.layoutSettings.paddingLeft + this.layoutSettings.paddingRight; + } + + public void setX(int x, int width) { + float f = (float) this.layoutSettings.paddingLeft; + float f1 = (float) (width - this.child.getWidth() - this.layoutSettings.paddingRight); + int i = (int) Utils.lerp(this.layoutSettings.xAlignment, f, f1); + this.child.setX(i + x); + } + + public void setY(int y, int height) { + float f = (float) this.layoutSettings.paddingTop; + float f1 = (float) (height - this.child.getHeight() - this.layoutSettings.paddingBottom); + int i = Math.round(Utils.lerp(this.layoutSettings.yAlignment, f, f1)); + this.child.setY(i + y); + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/BorderedLinearLayout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/BorderedLinearLayout.java new file mode 100644 index 000000000..42ee94e2c --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/BorderedLinearLayout.java @@ -0,0 +1,54 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +/** + * Author: MrCrayfish + */ +public class BorderedLinearLayout extends LinearLayout { + private int border; + + public BorderedLinearLayout(Orientation orientation) { + super(0, 0, orientation); + } + + public BorderedLinearLayout border(int size) { + int diff = size - this.border; + this.setX(this.getX() + diff); + this.setY(this.getY() + diff); + this.border = size; + return this; + } + + @Override + public int getX() { + return super.getX() - this.border; + } + + @Override + public int getY() { + return super.getY() - this.border; + } + + @Override + public void setX(int x) { + super.setX(x + this.border); + } + + @Override + public void setY(int y) { + super.setY(y + this.border); + } + + @Override + public int getWidth() { + return super.getWidth() + this.border + this.border; + } + + @Override + public int getHeight() { + return super.getHeight() + this.border + this.border; + } + + public static BorderedLinearLayout vertical() { + return new BorderedLinearLayout(LinearLayout.Orientation.VERTICAL); + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/Divisor.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/Divisor.java new file mode 100644 index 000000000..2104f6444 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/Divisor.java @@ -0,0 +1,51 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +import com.google.common.annotations.VisibleForTesting; +import it.unimi.dsi.fastutil.ints.IntIterator; + +import java.util.NoSuchElementException; + +public class Divisor implements IntIterator { + private final int denominator; + private final int quotient; + private final int mod; + private int returnedParts; + private int remainder; + + public Divisor(int numerator, int denominator) { + this.denominator = denominator; + if (denominator > 0) { + this.quotient = numerator / denominator; + this.mod = numerator % denominator; + } else { + this.quotient = 0; + this.mod = 0; + } + + } + + public boolean hasNext() { + return this.returnedParts < this.denominator; + } + + public int nextInt() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } else { + int i = this.quotient; + this.remainder += this.mod; + if (this.remainder >= this.denominator) { + this.remainder -= this.denominator; + ++i; + } + + ++this.returnedParts; + return i; + } + } + + @VisibleForTesting + public static Iterable asIterable(int numerator, int denominator) { + return () -> new Divisor(numerator, denominator); + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/GridLayout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/GridLayout.java new file mode 100644 index 000000000..4c88f288f --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/GridLayout.java @@ -0,0 +1,227 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +import com.cleanroommc.catalogue.Utils; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@SideOnly(Side.CLIENT) +public class GridLayout extends AbstractLayout { + private final List children = new ArrayList<>(); + private final List cellInhabitants = new ArrayList<>(); + private final LayoutSettings defaultCellSettings = LayoutSettings.defaults(); + private int rowSpacing = 0; + private int columnSpacing = 0; + + public GridLayout() { + this(0, 0); + } + + public GridLayout(int x, int y) { + super(x, y, 0, 0); + } + + @Override + public void arrangeElements() { + super.arrangeElements(); + int i = 0; + int j = 0; + + for (GridLayout.CellInhabitant gridlayout$cellinhabitant : this.cellInhabitants) { + i = Math.max(gridlayout$cellinhabitant.getLastOccupiedRow(), i); + j = Math.max(gridlayout$cellinhabitant.getLastOccupiedColumn(), j); + } + + int[] aint = new int[j + 1]; + int[] aint1 = new int[i + 1]; + + for (GridLayout.CellInhabitant gridlayout$cellinhabitant1 : this.cellInhabitants) { + int k = gridlayout$cellinhabitant1.getHeight() - (gridlayout$cellinhabitant1.occupiedRows - 1) * this.rowSpacing; + Divisor divisor = new Divisor(k, gridlayout$cellinhabitant1.occupiedRows); + + for (int l = gridlayout$cellinhabitant1.row; l <= gridlayout$cellinhabitant1.getLastOccupiedRow(); l++) { + aint1[l] = Math.max(aint1[l], divisor.nextInt()); + } + + int l1 = gridlayout$cellinhabitant1.getWidth() - (gridlayout$cellinhabitant1.occupiedColumns - 1) * this.columnSpacing; + Divisor divisor1 = new Divisor(l1, gridlayout$cellinhabitant1.occupiedColumns); + + for (int i1 = gridlayout$cellinhabitant1.column; i1 <= gridlayout$cellinhabitant1.getLastOccupiedColumn(); i1++) { + aint[i1] = Math.max(aint[i1], divisor1.nextInt()); + } + } + + int[] aint2 = new int[j + 1]; + int[] aint3 = new int[i + 1]; + aint2[0] = 0; + + for (int j1 = 1; j1 <= j; j1++) { + aint2[j1] = aint2[j1 - 1] + aint[j1 - 1] + this.columnSpacing; + } + + aint3[0] = 0; + + for (int k1 = 1; k1 <= i; k1++) { + aint3[k1] = aint3[k1 - 1] + aint1[k1 - 1] + this.rowSpacing; + } + + for (GridLayout.CellInhabitant gridlayout$cellinhabitant2 : this.cellInhabitants) { + int i2 = 0; + + for (int j2 = gridlayout$cellinhabitant2.column; j2 <= gridlayout$cellinhabitant2.getLastOccupiedColumn(); j2++) { + i2 += aint[j2]; + } + + i2 += this.columnSpacing * (gridlayout$cellinhabitant2.occupiedColumns - 1); + gridlayout$cellinhabitant2.setX(this.getX() + aint2[gridlayout$cellinhabitant2.column], i2); + int k2 = 0; + + for (int l2 = gridlayout$cellinhabitant2.row; l2 <= gridlayout$cellinhabitant2.getLastOccupiedRow(); l2++) { + k2 += aint1[l2]; + } + + k2 += this.rowSpacing * (gridlayout$cellinhabitant2.occupiedRows - 1); + gridlayout$cellinhabitant2.setY(this.getY() + aint3[gridlayout$cellinhabitant2.row], k2); + } + + this.width = aint2[j] + aint[j]; + this.height = aint3[i] + aint1[i]; + } + + public T addChild(T child, int row, int column) { + return this.addChild(child, row, column, this.newCellSettings()); + } + + public T addChild(T child, int row, int column, LayoutSettings layoutSettings) { + return this.addChild(child, row, column, 1, 1, layoutSettings); + } + + public T addChild(T child, int row, int column, Consumer layoutSettingsFactory) { + return this.addChild(child, row, column, 1, 1, Utils.make(this.newCellSettings(), layoutSettingsFactory)); + } + + public T addChild(T child, int row, int column, int occupiedRows, int occupiedColumns) { + return this.addChild(child, row, column, occupiedRows, occupiedColumns, this.newCellSettings()); + } + + public T addChild(T child, int row, int column, int occupiedRows, int occupiedColumns, LayoutSettings layoutSettings) { + if (occupiedRows < 1) { + throw new IllegalArgumentException("Occupied rows must be at least 1"); + } else if (occupiedColumns < 1) { + throw new IllegalArgumentException("Occupied columns must be at least 1"); + } else { + this.cellInhabitants.add(new GridLayout.CellInhabitant(child, row, column, occupiedRows, occupiedColumns, layoutSettings)); + this.children.add(child); + return child; + } + } + + public T addChild(T child, int row, int column, int occupiedRows, int occupiedColumns, Consumer layoutSettingsFactory) { + return this.addChild(child, row, column, occupiedRows, occupiedColumns, Utils.make(this.newCellSettings(), layoutSettingsFactory)); + } + + public GridLayout columnSpacing(int columnSpacing) { + this.columnSpacing = columnSpacing; + return this; + } + + public GridLayout rowSpacing(int rowSpacing) { + this.rowSpacing = rowSpacing; + return this; + } + + public GridLayout spacing(int spacing) { + return this.columnSpacing(spacing).rowSpacing(spacing); + } + + @Override + public void visitChildren(Consumer visitor) { + this.children.forEach(visitor); + } + + public LayoutSettings newCellSettings() { + return this.defaultCellSettings.copy(); + } + + public LayoutSettings defaultCellSetting() { + return this.defaultCellSettings; + } + + public GridLayout.RowHelper createRowHelper(int columns) { + return new GridLayout.RowHelper(columns); + } + + @SideOnly(Side.CLIENT) + static class CellInhabitant extends AbstractLayout.AbstractChildWrapper { + final int row; + final int column; + final int occupiedRows; + final int occupiedColumns; + + CellInhabitant(LayoutElement child, int row, int column, int occupiedRows, int occupiedColumns, LayoutSettings layoutSettings) { + super(child, layoutSettings.getExposed()); + this.row = row; + this.column = column; + this.occupiedRows = occupiedRows; + this.occupiedColumns = occupiedColumns; + } + + public int getLastOccupiedRow() { + return this.row + this.occupiedRows - 1; + } + + public int getLastOccupiedColumn() { + return this.column + this.occupiedColumns - 1; + } + } + + @SideOnly(Side.CLIENT) + public final class RowHelper { + private final int columns; + private int index; + + RowHelper(int columns) { + this.columns = columns; + } + + public T addChild(T child) { + return this.addChild(child, 1); + } + + public T addChild(T child, int occupiedColumns) { + return this.addChild(child, occupiedColumns, this.defaultCellSetting()); + } + + public T addChild(T child, LayoutSettings layoutSettings) { + return this.addChild(child, 1, layoutSettings); + } + + public T addChild(T child, int occupiedColumns, LayoutSettings layoutSettings) { + int i = this.index / this.columns; + int j = this.index % this.columns; + if (j + occupiedColumns > this.columns) { + i++; + j = 0; + this.index = Utils.roundToward(this.index, this.columns); + } + + this.index += occupiedColumns; + return GridLayout.this.addChild(child, i, j, 1, occupiedColumns, layoutSettings); + } + + public GridLayout getGrid() { + return GridLayout.this; + } + + public LayoutSettings newCellSettings() { + return GridLayout.this.newCellSettings(); + } + + public LayoutSettings defaultCellSetting() { + return GridLayout.this.defaultCellSetting(); + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/Layout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/Layout.java new file mode 100644 index 000000000..85e5693f6 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/Layout.java @@ -0,0 +1,24 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.function.Consumer; + +@SideOnly(Side.CLIENT) +public interface Layout extends LayoutElement { + void visitChildren(Consumer var1); + + default void visitWidgets(Consumer consumer) { + this.visitChildren((p_270634_) -> p_270634_.visitWidgets(consumer)); + } + + default void arrangeElements() { + this.visitChildren((p_270565_) -> { + if (p_270565_ instanceof Layout layout) { + layout.arrangeElements(); + } + + }); + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutElement.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutElement.java new file mode 100644 index 000000000..b571531c6 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutElement.java @@ -0,0 +1,32 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.function.Consumer; + +@SideOnly(Side.CLIENT) +public interface LayoutElement { + void setX(int x); + + void setY(int y); + + int getX(); + + int getY(); + + int getWidth(); + + int getHeight(); + + default void setPosition(int x, int y) { + setX(x); + setY(y); + } + + default ScreenRectangle getRectangle() { + return new ScreenRectangle(this.getX(), this.getY(), this.getWidth(), this.getHeight()); + } + + void visitWidgets(Consumer consumer); +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutSettings.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutSettings.java new file mode 100644 index 000000000..d1b663c8e --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutSettings.java @@ -0,0 +1,150 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public interface LayoutSettings { + LayoutSettings padding(int padding); + + LayoutSettings padding(int horizontalPadding, int verticalPadding); + + LayoutSettings padding(int paddingLeft, int paddingTop, int paddingRight, int paddingBottom); + + LayoutSettings paddingLeft(int paddingLeft); + + LayoutSettings paddingTop(int paddingTop); + + LayoutSettings paddingRight(int paddingRight); + + LayoutSettings paddingBottom(int paddingBottom); + + LayoutSettings paddingHorizontal(int horizontalPadding); + + LayoutSettings paddingVertical(int verticalPadding); + + LayoutSettings align(float xAlignment, float yAlignment); + + LayoutSettings alignHorizontally(float xAlignment); + + LayoutSettings alignVertically(float yAlignment); + + default LayoutSettings alignHorizontallyLeft() { + return this.alignHorizontally(0.0F); + } + + default LayoutSettings alignHorizontallyCenter() { + return this.alignHorizontally(0.5F); + } + + default LayoutSettings alignHorizontallyRight() { + return this.alignHorizontally(1.0F); + } + + default LayoutSettings alignVerticallyTop() { + return this.alignVertically(0.0F); + } + + default LayoutSettings alignVerticallyMiddle() { + return this.alignVertically(0.5F); + } + + default LayoutSettings alignVerticallyBottom() { + return this.alignVertically(1.0F); + } + + LayoutSettings copy(); + + LayoutSettings.LayoutSettingsImpl getExposed(); + + static LayoutSettings defaults() { + return new LayoutSettings.LayoutSettingsImpl(); + } + + @SideOnly(Side.CLIENT) + public static class LayoutSettingsImpl implements LayoutSettings { + public int paddingLeft; + public int paddingTop; + public int paddingRight; + public int paddingBottom; + public float xAlignment; + public float yAlignment; + + public LayoutSettingsImpl() { + } + + public LayoutSettingsImpl(LayoutSettings.LayoutSettingsImpl other) { + this.paddingLeft = other.paddingLeft; + this.paddingTop = other.paddingTop; + this.paddingRight = other.paddingRight; + this.paddingBottom = other.paddingBottom; + this.xAlignment = other.xAlignment; + this.yAlignment = other.yAlignment; + } + + public LayoutSettings.LayoutSettingsImpl padding(int padding) { + return this.padding(padding, padding); + } + + public LayoutSettings.LayoutSettingsImpl padding(int horizontalPadding, int verticalPadding) { + return this.paddingHorizontal(horizontalPadding).paddingVertical(verticalPadding); + } + + public LayoutSettings.LayoutSettingsImpl padding(int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { + return this.paddingLeft(paddingLeft).paddingRight(paddingRight).paddingTop(paddingTop).paddingBottom(paddingBottom); + } + + public LayoutSettings.LayoutSettingsImpl paddingLeft(int paddingLeft) { + this.paddingLeft = paddingLeft; + return this; + } + + public LayoutSettings.LayoutSettingsImpl paddingTop(int paddingTop) { + this.paddingTop = paddingTop; + return this; + } + + public LayoutSettings.LayoutSettingsImpl paddingRight(int paddingRight) { + this.paddingRight = paddingRight; + return this; + } + + public LayoutSettings.LayoutSettingsImpl paddingBottom(int paddingBottom) { + this.paddingBottom = paddingBottom; + return this; + } + + public LayoutSettings.LayoutSettingsImpl paddingHorizontal(int horizontalPadding) { + return this.paddingLeft(horizontalPadding).paddingRight(horizontalPadding); + } + + public LayoutSettings.LayoutSettingsImpl paddingVertical(int verticalPadding) { + return this.paddingTop(verticalPadding).paddingBottom(verticalPadding); + } + + public LayoutSettings.LayoutSettingsImpl align(float xAlignment, float yAlignment) { + this.xAlignment = xAlignment; + this.yAlignment = yAlignment; + return this; + } + + public LayoutSettings.LayoutSettingsImpl alignHorizontally(float xAlignment) { + this.xAlignment = xAlignment; + return this; + } + + public LayoutSettings.LayoutSettingsImpl alignVertically(float yAlignment) { + this.yAlignment = yAlignment; + return this; + } + + public LayoutSettings.LayoutSettingsImpl copy() { + return new LayoutSettings.LayoutSettingsImpl(this); + } + + @Override + public LayoutSettings.LayoutSettingsImpl getExposed() { + return this; + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java new file mode 100644 index 000000000..f714f68f9 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java @@ -0,0 +1,119 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +import com.cleanroommc.catalogue.Utils; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.function.Consumer; + +@SideOnly(Side.CLIENT) +public class LinearLayout implements Layout { + private final GridLayout wrapped; + private final LinearLayout.Orientation orientation; + private int nextChildIndex = 0; + + private LinearLayout(LinearLayout.Orientation orientation) { + this(0, 0, orientation); + } + + public LinearLayout(int width, int height, LinearLayout.Orientation orientation) { + this.wrapped = new GridLayout(width, height); + this.orientation = orientation; + } + + public LinearLayout spacing(int spacing) { + this.orientation.setSpacing(this.wrapped, spacing); + return this; + } + + public LayoutSettings newCellSettings() { + return this.wrapped.newCellSettings(); + } + + public LayoutSettings defaultCellSetting() { + return this.wrapped.defaultCellSetting(); + } + + public T addChild(T child, LayoutSettings layoutSettings) { + return this.orientation.addChild(this.wrapped, child, this.nextChildIndex++, layoutSettings); + } + + public T addChild(T child) { + return this.addChild(child, this.newCellSettings()); + } + + public T addChild(T child, Consumer layoutSettingsFactory) { + return this.orientation.addChild(this.wrapped, child, this.nextChildIndex++, Utils.make(this.newCellSettings(), layoutSettingsFactory)); + } + + @Override + public void visitChildren(Consumer visitor) { + this.wrapped.visitChildren(visitor); + } + + @Override + public void arrangeElements() { + this.wrapped.arrangeElements(); + } + + @Override + public int getWidth() { + return this.wrapped.getWidth(); + } + + @Override + public int getHeight() { + return this.wrapped.getHeight(); + } + + @Override + public void setX(int x) { + this.wrapped.setX(x); + } + + @Override + public void setY(int y) { + this.wrapped.setY(y); + } + + @Override + public int getX() { + return this.wrapped.getX(); + } + + @Override + public int getY() { + return this.wrapped.getY(); + } + + public static LinearLayout vertical() { + return new LinearLayout(LinearLayout.Orientation.VERTICAL); + } + + public static LinearLayout horizontal() { + return new LinearLayout(LinearLayout.Orientation.HORIZONTAL); + } + + @SideOnly(Side.CLIENT) + public static enum Orientation { + HORIZONTAL, + VERTICAL; + + void setSpacing(GridLayout layout, int spacing) { + switch (this) { + case HORIZONTAL: + layout.columnSpacing(spacing); + break; + case VERTICAL: + layout.rowSpacing(spacing); + } + } + + public T addChild(GridLayout layout, T element, int index, LayoutSettings layoutSettings) { + return (T) (switch (this) { + case HORIZONTAL -> (LayoutElement) layout.addChild(element, 0, index, layoutSettings); + case VERTICAL -> (LayoutElement) layout.addChild(element, index, 0, layoutSettings); + }); + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/ScreenRectangle.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/ScreenRectangle.java new file mode 100644 index 000000000..d4311fc64 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/ScreenRectangle.java @@ -0,0 +1,11 @@ +package com.cleanroommc.catalogue.client.screen.layout; + +public record ScreenRectangle(int left, int top, int width, int height) { + public int right() { + return left + width; + } + + public int bottom() { + return top + height; + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java deleted file mode 100644 index 3ac25d992..000000000 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueCheckBoxButton.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.cleanroommc.catalogue.client.screen.widget; - -import com.cleanroommc.catalogue.CatalogueConstants; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.util.ResourceLocation; - -/** - * Author: MrCrayfish - */ -public class CatalogueCheckBoxButton extends GuiButton { - private static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/checkbox.png"); - private boolean selected; - - public CatalogueCheckBoxButton(int id, int x, int y, boolean selectedDefault) { - super(id, x, y, 14, 14, ""); - this.selected = selectedDefault; - } - - @Override - public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY) { - if (this.enabled && this.visible && this.hovered) { - this.selected = !this.selected; - return true; - } else { - return false; - } - } - - @Override - public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partial) { - if (this.visible) { - this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; - minecraft.getTextureManager().bindTexture(TEXTURE); - - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.enableBlend(); - GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); - GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - drawModalRectWithCustomSizedTexture(this.x, this.y, this.hovered ? 14 : 0, this.selected() ? 14 : 0, 14, 14, 64, 64); - this.mouseDragged(minecraft, mouseX, mouseY); - } - } - - public boolean selected() { - return this.selected; - } - - public void setSelected(boolean selected) { - this.selected = selected; - } -} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index e669be141..9668d9b66 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -3,16 +3,14 @@ import com.cleanroommc.catalogue.CatalogueConstants; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.MathHelper; /** * Author: MrCrayfish */ -public class CatalogueIconButton extends GuiButton { - private static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/icons.png"); +public class CatalogueIconButton extends CatalogueTextButton { + public static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/icons.png"); private final String label; private final int u, v; @@ -52,22 +50,7 @@ public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partia GlStateManager.color(brightness, brightness, brightness, 1.0F); drawModalRectWithCustomSizedTexture(iconX, iconY, this.u, this.v, 10, 10, 64, 64); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - int textColor = this.getFGColor() | MathHelper.ceil(255.0F) << 24; - drawString(fontrenderer, this.label, iconX + 14, iconY + 1, textColor); + drawString(fontrenderer, this.label, iconX + 14, iconY + 1, this.getFGColor()); } - } - - private int getFGColor() { - if (packedFGColour != 0) { - return packedFGColour; - } else if (!this.enabled) { - return 10526880; - } else if (this.hovered) { - return 16777120; - } else { - return 14737632; - } - } - } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index 3e2565b8f..c518a8732 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -98,7 +98,7 @@ protected void drawScrollBar(Tessellator tessellator, int maxScroll) { int scrollThumbHeight = (this.bottom - this.top) * (this.bottom - this.top) / this.getContentHeight(); scrollThumbHeight = MathHelper.clamp(scrollThumbHeight, 32, this.bottom - this.top - 8); - int scrollThumbTop = (int)this.amountScrolled * (this.bottom - this.top - scrollThumbHeight) / maxScroll + this.top; + int scrollThumbTop = (int) this.amountScrolled * (this.bottom - this.top - scrollThumbHeight) / maxScroll + this.top; scrollThumbTop = Math.max(scrollThumbTop, this.top); // Background @@ -165,10 +165,16 @@ protected void drawSelectionBox(int mouseX, int mouseY, float partialTicks) { protected void drawSelectionBox(int contentLeft, int contentTop, int mouseXIn, int mouseYIn, float partialTicks) { } - public void setAmountScrolled(int amountScrolled) { - this.amountScrolled = (float)amountScrolled; - this.bindAmountScrolled(); - this.initialClickY = -2; + public void setClampedAmountScrolled(float scroll) { + this.amountScrolled = MathHelper.clamp(scroll, 0.0F, this.getMaxScroll()); + } + + public void setAmountScrolled(float scroll) { + this.setClampedAmountScrolled(scroll); + } + + public void clampAmountScrolled() { + this.setClampedAmountScrolled(this.getAmountScrolled()); } public void setWidth(int width) { @@ -194,11 +200,11 @@ protected int getListRight() { } protected int getListTop() { - return this.top + 4 - (int)this.amountScrolled; + return this.top + 4 - (int) this.amountScrolled; } protected int getRowTop(int pIndex) { - return this.top + 4 - (int)this.amountScrolled + pIndex * this.slotHeight + this.headerPadding; + return this.top + 4 - (int) this.amountScrolled + pIndex * this.slotHeight + this.headerPadding; } protected int getRowBottom(int pIndex) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java new file mode 100644 index 000000000..4693e2ec1 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java @@ -0,0 +1,78 @@ +package com.cleanroommc.catalogue.client.screen.widget; + +import com.cleanroommc.catalogue.Utils; +import com.cleanroommc.catalogue.client.ClientHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; + +public class CatalogueTextButton extends GuiButton { + protected static final WidgetSprites SPRITES = new WidgetSprites(Utils.withDefaultNamespace("widget/button"), Utils.withDefaultNamespace("widget/button_disabled"), Utils.withDefaultNamespace("widget/button_highlighted")); + + public CatalogueTextButton(int buttonId, int x, int y, int widthIn, int heightIn, String buttonText) { + super(buttonId, x, y, widthIn, heightIn, buttonText); + } + + public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTicks) { + if (!this.visible) return; + this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + mc.getTextureManager().bindTexture(SPRITES.get(this.enabled, this.hovered)); + ClientHelper.blitNineSlicedSprite(new ClientHelper.NineSlice(200, 20, 3), this.x, this.y, this.width, this.height); + + this.mouseDragged(mc, mouseX, mouseY); + this.renderString(mc.fontRenderer, this.getFGColor()); + } + + public void renderString(FontRenderer font, int color) { + this.renderScrollingString(font, 2, color); + } + + protected void renderScrollingString(FontRenderer font, int width, int color) { + int i = this.x + width; + int j = this.x + this.width - width; + renderScrollingString(font, this.displayString, i, this.y, j, this.y + this.height, color); + } + + public void renderScrollingString(FontRenderer font, String text, int minX, int minY, int maxX, int maxY, int color) { + renderScrollingString(font, text, (minX + maxX) / 2, minX, minY, maxX, maxY, color); + } + + public void renderScrollingString(FontRenderer font, String text, int centerX, int minX, int minY, int maxX, int maxY, int color) { + int i = font.getStringWidth(text); + int j = (minY + maxY - 9) / 2 + 1; + int k = maxX - minX; + if (i > k) { + int l = i - k; + double d0 = (double) System.currentTimeMillis() / (double) 1000.0F; + double d1 = Math.max((double) l * (double) 0.5F, (double) 3.0F); + double d2 = Math.sin((Math.PI / 2D) * Math.cos((Math.PI * 2D) * d0 / d1)) / (double) 2.0F + (double) 0.5F; + double d3 = Utils.lerp(d2, 0.0F, l); + ClientHelper.scissor(minX, minY, maxX - minX, maxY - minY); + drawString(font, text, minX - (int) d3, j, color); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + } else { + int i1 = MathHelper.clamp(centerX, minX + i / 2, maxX - i / 2); + drawCenteredString(font, text, i1, j, color); + } + } + + protected int getFGColor() { + if (packedFGColour != 0) { + return packedFGColour; + } else if (!this.enabled) { + return 10526880; + } else if (this.hovered) { + return 16777120; + } else { + return 14737632; + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java index 218770f6e..d68b4c7dc 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java @@ -3,14 +3,19 @@ import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.GuiTextField; +import org.jetbrains.annotations.Nullable; -public class CatalogueTextField extends GuiTextField { - // GuiTextField with suggestions. - // Also, lit it! +import java.util.function.BiFunction; +import java.util.function.Consumer; +public class CatalogueTextField extends GuiTextField { private final FontRenderer fontRenderer; private String suggestion = ""; private boolean isTextTruncated; + @Nullable + private Consumer responder; + @Nullable + private BiFunction formatter; public CatalogueTextField(int id, FontRenderer fontRenderer, int x, int y, int width, int height) { super(id, fontRenderer, x, y, width, height); @@ -48,8 +53,8 @@ public void drawTextBox() { // Draw text before cursor if (!visibleText.isEmpty()) { - String text = isCursorVisible ? visibleText.substring(0, cursorPosRelative) : visibleText; - currentDrawX = this.fontRenderer.drawStringWithShadow(text, (float) textStartX, (float) textStartY, textColor); + String rawTextBeforeCursor = isCursorVisible ? visibleText.substring(0, cursorPosRelative) : visibleText; + currentDrawX = this.fontRenderer.drawStringWithShadow(formatText(rawTextBeforeCursor, this.lineScrollOffset), (float) textStartX, (float) textStartY, textColor); } isTextTruncated = this.cursorPosition < this.getText().length() || this.getText().length() >= this.getMaxStringLength(); @@ -64,7 +69,8 @@ public void drawTextBox() { // Draw text after cursor if (!visibleText.isEmpty() && isCursorVisible && cursorPosRelative < visibleText.length()) { - currentDrawX = this.fontRenderer.drawStringWithShadow(visibleText.substring(cursorPosRelative), (float) currentDrawX, (float) textStartY, textColor); + String rawTextAfterCursor = visibleText.substring(cursorPosRelative); + currentDrawX = this.fontRenderer.drawStringWithShadow(formatText(rawTextAfterCursor, this.cursorPosition), (float) currentDrawX, (float) textStartY, textColor); } if (!isTextTruncated && this.suggestion != null) { @@ -89,6 +95,49 @@ public void drawTextBox() { } } + // Formatter + public void setFormatter(@Nullable BiFunction pFormatter) { + this.formatter = pFormatter; + } + + private String formatText(String text, int cursorPos) { + return formatter != null ? formatter.apply(text, cursorPos) : text; + } + + // Responder + public void setResponder(@Nullable Consumer pResponder) { + this.responder = pResponder; + } + + private void onTextChange(String textIn) { + if (this.responder != null) { + this.responder.accept(textIn); + } + } + + @Override + public void setText(String textIn) { + super.setText(textIn); + if (this.validator.apply(textIn)) { + this.onTextChange(textIn); + } + } + + @Override + public void setMaxStringLength(int length) { + super.setMaxStringLength(length); + if (this.getText().length() > length) { + this.onTextChange(this.getText()); + } + } + + @Override + public void moveCursorBy(int num) { + super.moveCursorBy(num); + this.onTextChange(this.getText()); + } + + // Suggestion public void setSuggestion(String suggestion) { this.suggestion = suggestion; } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java new file mode 100644 index 000000000..219ca7656 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java @@ -0,0 +1,488 @@ +package com.cleanroommc.catalogue.client.screen.widget; + +import com.cleanroommc.catalogue.CatalogueConstants; +import com.cleanroommc.catalogue.Utils; +import com.cleanroommc.catalogue.client.ClientHelper; +import com.cleanroommc.catalogue.client.screen.DropdownMenuHandler; +import com.cleanroommc.catalogue.client.screen.layout.BorderedLinearLayout; +import com.cleanroommc.catalogue.client.screen.layout.LayoutElement; +import com.cleanroommc.catalogue.client.screen.layout.ScreenRectangle; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.audio.SoundHandler; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.init.SoundEvents; +import net.minecraft.util.ResourceLocation; +import org.apache.commons.lang3.mutable.MutableBoolean; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Author: MrCrayfish + */ +public class DropdownMenu extends Gui implements LayoutElement { + private final DropdownMenuHandler handler; + private final BorderedLinearLayout layout = (BorderedLinearLayout) + BorderedLinearLayout.vertical().border(1).spacing(1); + private final List items = new ArrayList<>(); + private Alignment alignment = Alignment.BELOW_LEFT; + private @Nullable DropdownMenu parent; + private @Nullable DropdownMenu subMenu; + + public boolean active; + public boolean visible; + + public int x; + public int y; + public int width; + public int height; + + private DropdownMenu(DropdownMenuHandler handler) { + super(); + this.handler = handler; + this.visible = false; + this.active = true; + } + + private void setAlignment(Alignment alignment) { + this.alignment = alignment; + } + + public void toggle(int mouseX, int mouseY) { + this.toggle(new ScreenRectangle(mouseX, mouseY, 0, 0)); + } + + public void toggle(GuiButton widget) { + this.toggle(new ScreenRectangle(widget.x, widget.y, widget.width, widget.height)); + } + + public void toggle(ScreenRectangle rect) { + if (!this.visible) { + this.show(rect); + } else { + this.hide(); + } + } + + private void show(ScreenRectangle rect) { + this.updatePosition(rect); + this.items.forEach(child -> child.visible = true); + this.visible = true; + if (this.parent == null) { + this.handler.setMenu(this); + } + } + + public void hide() { + this.items.forEach(child -> { + child.visible = false; + if (child instanceof DropdownItem menu) { + menu.subMenu.hide(); + } + }); + this.subMenu = null; + this.visible = false; + } + + private void updatePosition(ScreenRectangle rect) { + this.layout.arrangeElements(); + this.width = this.layout.getWidth(); + this.height = this.layout.getHeight(); + this.alignment.aligner.accept(this, rect); + this.layout.setX(this.getX()); + this.layout.setY(this.getY()); + } + + public void addItem(MenuItem item) { + this.layout.addChild(item); + this.items.add(item); + item.visible = false; + } + + private void deepClose() { + this.handler.setMenu(null); + } + + public void drawScreen(Minecraft minecraft, int mouseX, int mouseY, float deltaTick) { + GlStateManager.pushMatrix(); + drawRect(0, 0, minecraft.displayWidth, minecraft.displayHeight, 0x50000000); + drawRect(this.x, this.y, this.x + this.width, this.y + this.height, 0xAA000000); + this.items.forEach(widget -> widget.drawWidget(minecraft, mouseX, mouseY, deltaTick)); + if (this.subMenu != null) { + this.subMenu.drawScreen(minecraft, mouseX, mouseY, deltaTick); + } + GlStateManager.popMatrix(); + } + + public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY) { + if (!this.active || !this.visible) return false; + + AtomicBoolean clicked = new AtomicBoolean(); + this.layout.visitWidgets(widget -> { + if (widget instanceof MenuItem item && item.mousePressed(minecraft, mouseX, mouseY)) { + clicked.set(true); + } + }); + if (this.subMenu != null && this.subMenu.mousePressed(minecraft, mouseX, mouseY)) { + clicked.set(true); + } + return clicked.get(); + } + + public void visitWidgets(Consumer consumer) { + this.layout.visitWidgets(consumer); + } + + public static class MenuItem extends Gui implements LayoutElement { + static final WidgetSprites SPRITES = new WidgetSprites( + Utils.withDefaultNamespace("dropdown/item"), + Utils.withDefaultNamespace("dropdown/item_highlighted") + ); + protected final DropdownMenu parent; + private final Runnable onClick; + + private int width; + private int height; + private int x; + private int y; + public String label; + public boolean enabled; + public boolean visible; + protected boolean hovered; + + public MenuItem(DropdownMenu menu, String label, Runnable onClick) { + super(); + this.x = 0; + this.y = 0; + this.width = 100; + this.height = 20; + this.enabled = true; + this.visible = true; + this.label = label; + this.parent = menu; + this.onClick = onClick; + } + + protected boolean selected() { + return false; + } + + public void drawWidget(Minecraft minecraft, int mouseX, int mouseY, float deltaTick) { + if (!this.visible) return; + this.hovered = mouseX >= this.getX() && mouseY >= this.getY() && mouseX < this.getX() + this.getWidth() && mouseY < this.getY() + this.height; + + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + minecraft.getTextureManager().bindTexture(SPRITES.get(this.enabled, this.hovered || this.selected())); + ClientHelper.blitNineSlicedSprite(new ClientHelper.NineSlice(12, 12, 2), this.getX(), this.getY(), this.getWidth(), this.getHeight()); + + FontRenderer font = minecraft.fontRenderer; + int offset = (this.getHeight() - font.FONT_HEIGHT) / 2 + 1; + drawString(font, this.label, this.getX() + offset, this.getY() + offset, 0xFFFFFFFF); + } + + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { + if (this.enabled && this.visible && this.hovered) { + this.onClick(mouseX, mouseY); + this.playPressSound(mc.getSoundHandler()); + return true; + } + return false; + } + + public void onClick(int mouseX, int mouseY) { + this.onClick.run(); + this.parent.deepClose(); + } + + protected int calculateWidth() { + FontRenderer font = Minecraft.getMinecraft().fontRenderer; + int labelOffset = (this.height - font.FONT_HEIGHT) / 2 + 1; + int labelWidth = font.getStringWidth(this.label); + return labelOffset + labelWidth + labelOffset; + } + + public boolean isMouseOver() { + return this.hovered; + } + + public void playPressSound(SoundHandler soundHandlerIn) { + soundHandlerIn.playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } + + @Override + public void setX(int pX) { + this.x = pX; + } + + @Override + public void setY(int pY) { + this.y = pY; + } + + @Override + public int getX() { + return this.x; + } + + @Override + public int getY() { + return this.y; + } + + @Override + public int getWidth() { + return this.width; + } + + @Override + public int getHeight() { + return this.height; + } + + @Override + public void setPosition(int pX, int pY) { + LayoutElement.super.setPosition(pX, pY); + } + + @Override + public void visitWidgets(Consumer pConsumer) { + pConsumer.accept(this); + } + } + + private static class CheckboxMenuItem extends MenuItem { + private static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/checkbox.png"); + + private final MutableBoolean holder; + private final Function callback; + + public CheckboxMenuItem(DropdownMenu menu, String label, MutableBoolean holder, Function callback) { + super(menu, label, () -> { + }); + this.holder = holder; + this.callback = callback; + } + + @Override + public void drawWidget(Minecraft minecraft, int mouseX, int mouseY, float deltaTick) { + super.drawWidget(minecraft, mouseX, mouseY, deltaTick); + int offset = (this.getHeight() - 14) / 2; + minecraft.getTextureManager().bindTexture(TEXTURE); + + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + drawModalRectWithCustomSizedTexture(this.getX() + this.getWidth() - 14 - offset, this.getY() + offset, this.hovered ? 14 : 0, this.holder.getValue() ? 14 : 0, 14, 14, 64, 64); + } + + @Override + public void onClick(int mouseX, int mouseY) { + boolean newValue = !this.holder.getValue(); + this.holder.setValue(newValue); + if (this.callback.apply(newValue)) { + this.parent.deepClose(); + } + } + + @Override + protected int calculateWidth() { + FontRenderer font = Minecraft.getMinecraft().fontRenderer; + int labelOffset = (this.getHeight() - font.FONT_HEIGHT) / 2 + 1; + int labelWidth = font.getStringWidth(this.label); + int checkboxOffset = (this.getHeight() - 14) / 2; + return labelOffset + labelWidth + labelOffset + 14 + checkboxOffset; + } + } + + private static class DropdownItem extends MenuItem { + private final DropdownMenu subMenu; + + public DropdownItem(DropdownMenu menu, DropdownMenu subMenu, String label) { + super(menu, label, () -> { + }); + this.subMenu = subMenu; + } + + @Override + public void drawWidget(Minecraft minecraft, int mouseX, int mouseY, float deltaTick) { + super.drawWidget(minecraft, mouseX, mouseY, deltaTick); + FontRenderer font = minecraft.fontRenderer; + int top = this.getY() + (this.getHeight() - font.FONT_HEIGHT) / 2 + 1; + drawString(font, ">", this.getX() + this.getWidth() - 10, top, 0xFFFFFFFF); + } + + @Override + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { + return super.mousePressed(mc, mouseX, mouseY) || this.subMenu.mousePressed(mc, mouseX, mouseY); + } + + @Override + public void onClick(int mouseX, int mouseY) { + if (this.parent.subMenu != null) { + this.parent.subMenu.hide(); + if (this.parent.subMenu == this.subMenu) { + this.parent.subMenu = null; + return; + } + } + this.parent.subMenu = this.subMenu; + this.subMenu.show(this.getRectangle()); + } + + @Override + public void visitWidgets(Consumer consumer) { + consumer.accept(this); + this.subMenu.visitWidgets(consumer); + } + + @Override + protected boolean selected() { + return this.parent.subMenu == this.subMenu; + } + + @Override + protected int calculateWidth() { + FontRenderer font = Minecraft.getMinecraft().fontRenderer; + int labelOffset = (this.getHeight() - font.FONT_HEIGHT) / 2 + 1; + int labelWidth = font.getStringWidth(this.label); + int arrowWidth = font.getStringWidth(">"); + return labelOffset + labelWidth + labelOffset + arrowWidth + labelOffset; + } + } + + private interface MenuAligner { + void accept(DropdownMenu menu, ScreenRectangle rectangle); + } + + public enum Alignment { + ABOVE_LEFT((menu, rectangle) -> { + menu.setX(rectangle.left()); + menu.setY(rectangle.top() - menu.getHeight()); + }), + ABOVE_RIGHT((menu, rectangle) -> { + menu.setX(rectangle.right() - menu.getWidth()); + menu.setY(rectangle.top() - menu.getHeight()); + }), + BELOW_LEFT((menu, rectangle) -> { + menu.setX(rectangle.left() - 1); + menu.setY(rectangle.bottom()); + }), + BELOW_RIGHT((menu, rectangle) -> { + menu.setX(rectangle.right() - menu.getWidth() + 1); + menu.setY(rectangle.bottom()); + }), + END_TOP((menu, rectangle) -> { + menu.setX(rectangle.right()); + menu.setY(rectangle.top() - 1); + }), + END_BOTTOM((menu, rectangle) -> { + menu.setX(rectangle.right()); + menu.setY(rectangle.bottom() - menu.getHeight() + 1); + }); + + + private final MenuAligner aligner; + + Alignment(MenuAligner positioner) { + this.aligner = positioner; + } + + } + + public static Builder builder(DropdownMenuHandler handler) { + return new Builder(handler); + } + + public static class Builder { + private final DropdownMenuHandler handler; + private final DropdownMenu base; + private final List items = new ArrayList<>(); + private int minItemWidth = 0; + private int minItemHeight = 20; + + private Builder(DropdownMenuHandler handler) { + this.handler = handler; + this.base = new DropdownMenu(handler); + } + + public Builder setMinItemSize(int width, int height) { + this.minItemWidth = width; + this.minItemHeight = height; + return this; + } + + public Builder setAlignment(Alignment alignment) { + this.base.setAlignment(alignment); + return this; + } + + public Builder addItem(String label, Runnable onClick) { + this.items.add(new MenuItem(this.base, label, onClick)); + return this; + } + + public Builder addCheckbox(String label, MutableBoolean holder, Function callback) { + this.items.add(new CheckboxMenuItem(this.base, label, holder, callback)); + return this; + } + + public Builder addMenu(String label, Builder builder) { + DropdownMenu menu = builder.build(); + menu.parent = this.base; + this.items.add(new DropdownItem(this.base, menu, label)); + return this; + } + + public DropdownMenu build() { + int maxWidth = this.items.stream().mapToInt(MenuItem::calculateWidth).max().orElse(100); + this.items.forEach(widget -> { + widget.width = Math.max(maxWidth, this.minItemWidth); + widget.height = this.minItemHeight; + this.base.addItem(widget); + }); + return this.base; + } + } + + @Override + public void setX(int x) { + this.x = x; + } + + @Override + public void setY(int y) { + this.y = y; + } + + @Override + public int getX() { + return this.x; + } + + @Override + public int getY() { + return this.y; + } + + @Override + public int getWidth() { + return this.width; + } + + @Override + public int getHeight() { + return this.height; + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/WidgetSprites.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/WidgetSprites.java new file mode 100644 index 000000000..84ade66b4 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/WidgetSprites.java @@ -0,0 +1,25 @@ +package com.cleanroommc.catalogue.client.screen.widget; + +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public record WidgetSprites(ResourceLocation enabled, ResourceLocation disabled, ResourceLocation enabledFocused, + ResourceLocation disabledFocused) { + public WidgetSprites(ResourceLocation normal, ResourceLocation focused) { + this(normal, normal, focused, focused); + } + + public WidgetSprites(ResourceLocation enabled, ResourceLocation disabled, ResourceLocation focused) { + this(enabled, disabled, focused, disabled); + } + + public ResourceLocation get(boolean enabled, boolean focused) { + if (enabled) { + return focused ? this.enabledFocused : this.enabled; + } else { + return focused ? this.disabledFocused : this.disabled; + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/exception/InvalidBrandingImageException.java b/src/main/java/com/cleanroommc/catalogue/exception/InvalidBrandingImageException.java new file mode 100644 index 000000000..9fd249375 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/exception/InvalidBrandingImageException.java @@ -0,0 +1,10 @@ +package com.cleanroommc.catalogue.exception; + +/** + * Author: MrCrayfish + */ +public class InvalidBrandingImageException extends RuntimeException { + public InvalidBrandingImageException(String message) { + super(message); + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/exception/ModResourceNotFoundException.java b/src/main/java/com/cleanroommc/catalogue/exception/ModResourceNotFoundException.java new file mode 100644 index 000000000..7e09ad006 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/exception/ModResourceNotFoundException.java @@ -0,0 +1,10 @@ +package com.cleanroommc.catalogue.exception; + +import java.io.IOException; + +/** + * Author: MrCrayfish + */ +public class ModResourceNotFoundException extends IOException { + +} diff --git a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java new file mode 100644 index 000000000..5877bbe64 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java @@ -0,0 +1,59 @@ +package com.cleanroommc.catalogue.platform; + +import com.cleanroommc.catalogue.CatalogueConfig; +import com.cleanroommc.catalogue.client.Branding; +import com.cleanroommc.catalogue.client.CleanroomModData; +import com.cleanroommc.catalogue.client.IModData; +import com.cleanroommc.catalogue.platform.services.IPlatformHelper; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraftforge.fml.common.Loader; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Author: MrCrayfish + */ +public class CleanroomPlatformHelper implements IPlatformHelper { + + @Override + public List getAllModData() { + return Loader.instance().getActiveModList().stream().map(CleanroomModData::new).collect(Collectors.toList()); + } + + @Override + public File getModDirectory() { + return Loader.instance().getConfigDir().getParentFile().toPath().resolve("mods").toFile(); + } + + @Override + public Path getConfigDirectory() { + return Loader.instance().getConfigDir().toPath(); + } + + @Override + public BufferedImage loadImageFromModResource(String modid, String resource) throws IOException { + InputStream is = getClass().getResourceAsStream(resource); + return is != null ? TextureUtil.readBufferedImage(is) : null; + } + + @Override + public boolean isModLoaded(String modId) { + return Loader.isModLoaded(modId); + } + + @Override + public boolean getEnableBannerLimit() { + return CatalogueConfig.enableBannerLimit; + } + + @Override + public Branding.BannerLimit getBannerLimit() { + return new Branding.BannerLimit(CatalogueConfig.bannerMaxWidth, CatalogueConfig.bannerMaxHeight); + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/platform/ClientServices.java b/src/main/java/com/cleanroommc/catalogue/platform/ClientServices.java new file mode 100644 index 000000000..26fc04a84 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/platform/ClientServices.java @@ -0,0 +1,16 @@ +package com.cleanroommc.catalogue.platform; + +import com.cleanroommc.catalogue.CatalogueConstants; +import com.cleanroommc.catalogue.platform.services.IPlatformHelper; + +import java.util.ServiceLoader; + +public class ClientServices { + public static final IPlatformHelper PLATFORM = load(IPlatformHelper.class); + + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz).findFirst().orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + CatalogueConstants.LOG.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/platform/services/IPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/services/IPlatformHelper.java new file mode 100644 index 000000000..79fffb2da --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/platform/services/IPlatformHelper.java @@ -0,0 +1,26 @@ +package com.cleanroommc.catalogue.platform.services; + +import com.cleanroommc.catalogue.client.Branding; +import com.cleanroommc.catalogue.client.IModData; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +public interface IPlatformHelper { + List getAllModData(); + + File getModDirectory(); + + Path getConfigDirectory(); + + BufferedImage loadImageFromModResource(String modid, String resource) throws IOException; + + boolean isModLoaded(String modId); + + boolean getEnableBannerLimit(); + + Branding.BannerLimit getBannerLimit(); +} diff --git a/src/main/java/com/cleanroommc/common/CatalogueContainer.java b/src/main/java/com/cleanroommc/common/CatalogueContainer.java index 77e7b333b..75d236aa0 100644 --- a/src/main/java/com/cleanroommc/common/CatalogueContainer.java +++ b/src/main/java/com/cleanroommc/common/CatalogueContainer.java @@ -23,9 +23,7 @@ public CatalogueContainer() { meta.credits = "Hatsondogs for creating icons"; meta.license = "MIT"; meta.logoFile = "/assets/catalogue/icon.png"; - meta.logoBlur = false; meta.iconFile = "/assets/catalogue/icon.png"; - meta.iconBlur = false; meta.backgroundFile = "/assets/catalogue/background.png"; ConfigManager.register(CatalogueConfig.class); MinecraftForge.EVENT_BUS.register(CatalogueConfig.class); diff --git a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java index 42625a99a..d51b0aa68 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java +++ b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java @@ -50,9 +50,7 @@ public class ModMetadata public String issueTrackerUrl = ""; public String logoFile = ""; - public boolean logoBlur = true; // Blur the edge of the logo to make it smooth. True by default. public String iconFile = ""; - public boolean iconBlur = true; public String iconItem = ""; // Customized item or block as icon. Will not applied when iconFile is valid. public String backgroundFile = ""; public String version = ""; diff --git a/src/main/resources/META-INF/services/com.cleanroommc.catalogue.platform.services.IPlatformHelper b/src/main/resources/META-INF/services/com.cleanroommc.catalogue.platform.services.IPlatformHelper new file mode 100644 index 000000000..9d58866a9 --- /dev/null +++ b/src/main/resources/META-INF/services/com.cleanroommc.catalogue.platform.services.IPlatformHelper @@ -0,0 +1 @@ +com.cleanroommc.catalogue.platform.CleanroomPlatformHelper \ No newline at end of file diff --git a/src/main/resources/assets/catalogue/lang/de_de.lang b/src/main/resources/assets/catalogue/lang/de_de.lang new file mode 100644 index 000000000..a3b9db1d0 --- /dev/null +++ b/src/main/resources/assets/catalogue/lang/de_de.lang @@ -0,0 +1,2 @@ +catalogue.gui.no_selection=No mod selected... +catalogue.gui.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! diff --git a/src/main/resources/assets/catalogue/lang/en_au.lang b/src/main/resources/assets/catalogue/lang/en_au.lang new file mode 100644 index 000000000..5646e584c --- /dev/null +++ b/src/main/resources/assets/catalogue/lang/en_au.lang @@ -0,0 +1,50 @@ +catalogue.gui.no_selection=No mod selected... +catalogue.gui.mod_list=Mods +catalogue.gui.search=Search +catalogue.gui.config=Config +catalogue.gui.open_mods_folder=Open Mods Folder +catalogue.gui.licenses=License(s): %s +catalogue.gui.authors=Author(s): %s +catalogue.gui.contributors=Contributor(s): %s +catalogue.gui.credits=Credits: %s +catalogue.gui.version=Version: %s +catalogue.gui.inner_version=Inner Version: %s +catalogue.gui.beta=This version is a beta version +catalogue.gui.ahead=This version is ahead of the latest version found (%s) +catalogue.gui.update_available=Update (%s): %s +catalogue.gui.update_available_no_page=Update (%s) +catalogue.gui.beta_update_available=Beta Update (%s): %s +catalogue.gui.beta_update_available_no_page=Beta Update (%s) +catalogue.gui.info=This menu is provided by Catalogue. Click here to open the CurseForge page for this mod! +catalogue.gui.favourite=Mark as Favourite +catalogue.gui.remove_favourite=Remove as Favourite +catalogue.gui.options=Options +catalogue.gui.filters=Filters +catalogue.gui.filters.configs_only=Mods with Configs +catalogue.gui.filters.updates_only=Mods with Updates +catalogue.gui.filters.favourites=Favourites Only +catalogue.gui.sort=Sort +catalogue.gui.sort.alphabetically=A to Z +catalogue.gui.sort.alphabetically_reverse=Z to A +catalogue.gui.sort.favourites_first=Favourites First +catalogue.gui.hide_libraries=Hide Libraries +catalogue.gui.no_mods=No mods... +catalogue.gui.mod_count=%s Mods +catalogue.gui.library_count=%s Libraries +catalogue.gui.advanced_search.info=Advanced search queries will ignore any active filters +catalogue.gui.show_dependencies=Show Dependencies +catalogue.gui.show_dependents=Show Dependents +catalogue.gui.website=Website +catalogue.gui.issue=Submit Bug +catalogue.gui.mod_id=Mod ID: %s + +catalogue.config.library_list=Library List +catalogue.config.library_list.tooltip=The list of library mods' mod ids.\nThey will have grey names in the mod list. +catalogue.config.ignored_dependencies_list=Ignored Dependencies List +catalogue.config.ignored_dependencies_list.tooltip=The list of ignored dependencies' mod ids.\nThey will not be displayed when searching for dependencies/dependants. +catalogue.config.enable_banner_limit=Enable Banner Limit +catalogue.config.enable_banner_limit.tooltip=Whether limit the size of mods' banners. +catalogue.config.banner_max_width=Banner Max Width +catalogue.config.banner_max_width.tooltip=The maximum of banner's width. Will not work if Enable Banner Limit is set false. +catalogue.config.banner_max_height=Banner Max Height +catalogue.config.banner_max_height.tooltip=The maximum of banner's height. Will not work if Enable Banner Limit is set false. diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index 0d6a2efeb..cc03cf674 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -1,25 +1,50 @@ -catalogue.gui.title=Mods -catalogue.gui.config=Config -catalogue.gui.website=Website -catalogue.gui.issue=Submit Bug -catalogue.gui.search=Search catalogue.gui.no_selection=No mod selected... -catalogue.gui.no_mods=No mods... -catalogue.gui.open_mods_folder=Open mods folder -catalogue.gui.filter_updates=Show only mods with updates -catalogue.gui.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! -catalogue.gui.version=Version: %s -catalogue.gui.mod_id=Mod ID: %s +catalogue.gui.mod_list=Mods +catalogue.gui.search=Search +catalogue.gui.config=Config +catalogue.gui.open_mods_folder=Open Mods Folder +catalogue.gui.licenses=License(s): %s catalogue.gui.authors=Author(s): %s +catalogue.gui.contributors=Contributor(s): %s catalogue.gui.credits=Credits: %s -catalogue.gui.license=License(s): %s +catalogue.gui.version=Version: %s +catalogue.gui.inner_version=Inner Version: %s catalogue.gui.beta=This version is a beta version catalogue.gui.ahead=This version is ahead of the latest version found (%s) -catalogue.gui.update_available=Update (%s) Available: %s -catalogue.gui.update_available_no_page=Update (%s) Available -catalogue.gui.beta_update_available=Beta Update (%s) Available: %s -catalogue.gui.beta_update_available_no_page=Beta Update (%s) Available +catalogue.gui.update_available=Update (%s): %s +catalogue.gui.update_available_no_page=Update (%s) +catalogue.gui.beta_update_available=Beta Update (%s): %s +catalogue.gui.beta_update_available_no_page=Beta Update (%s) +catalogue.gui.info=This menu is provided by Catalogue. Click here to open the CurseForge page for this mod! +catalogue.gui.favourite=Mark as Favorite +catalogue.gui.remove_favourite=Remove as Favorite +catalogue.gui.options=Options +catalogue.gui.filters=Filters +catalogue.gui.filters.configs_only=Mods with Configs +catalogue.gui.filters.updates_only=Mods with Updates +catalogue.gui.filters.favourites=Favorites Only +catalogue.gui.sort=Sort +catalogue.gui.sort.alphabetically=A to Z +catalogue.gui.sort.alphabetically_reverse=Z to A +catalogue.gui.sort.favourites_first=Favorites First +catalogue.gui.hide_libraries=Hide Libraries +catalogue.gui.no_mods=No mods... catalogue.gui.mod_count=%s Mods catalogue.gui.library_count=%s Libraries +catalogue.gui.advanced_search.info=Advanced search queries will ignore any active filters +catalogue.gui.show_dependencies=Show Dependencies +catalogue.gui.show_dependents=Show Dependents +catalogue.gui.website=Website +catalogue.gui.issue=Submit Bug +catalogue.gui.mod_id=Mod ID: %s catalogue.config.library_list=Library List +catalogue.config.library_list.tooltip=The list of library mods' mod ids.\nThey will have grey names in the mod list. +catalogue.config.ignored_dependencies_list=Ignored Dependencies List +catalogue.config.ignored_dependencies_list.tooltip=The list of ignored dependencies' mod ids.\nThey will not be displayed when searching for dependencies/dependants. +catalogue.config.enable_banner_limit=Enable Banner Limit +catalogue.config.enable_banner_limit.tooltip=Whether limit the size of mods' banners. +catalogue.config.banner_max_width=Banner Max Width +catalogue.config.banner_max_width.tooltip=The maximum of banner's width. Will not work if Enable Banner Limit is false. +catalogue.config.banner_max_height=Banner Max Height +catalogue.config.banner_max_height.tooltip=The maximum of banner's height. Will not work if Enable Banner Limit is false. diff --git a/src/main/resources/assets/catalogue/lang/pl_pl.lang b/src/main/resources/assets/catalogue/lang/pl_pl.lang new file mode 100644 index 000000000..683ffbe39 --- /dev/null +++ b/src/main/resources/assets/catalogue/lang/pl_pl.lang @@ -0,0 +1,2 @@ +catalogue.gui.no_selection=Brak wybranego moda... +catalogue.gui.info=To menu zostało przeprojektowane przez moda Catalogue. Kliknij tutaj, aby otworzyć stronę tego moda w serwisie CurseForge! diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index 14b411969..082d1dfa0 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -1,25 +1,50 @@ -catalogue.gui.title=模组 -catalogue.gui.config=配置 -catalogue.gui.website=网站 -catalogue.gui.issue=提交 Bug -catalogue.gui.search=搜索 catalogue.gui.no_selection=没有选择模组…… -catalogue.gui.no_mods=没有模组… -catalogue.gui.open_mods_folder=打开 Mods 文件夹 -catalogue.gui.filter_updates=仅显示有更新的模组 -catalogue.gui.info=此菜单由 Catalogue 提供。单击此处打开此模组的 CurseForge 页面! -catalogue.gui.version=版本: %s -catalogue.gui.mod_id=Mod ID: %s -catalogue.gui.authors=作者: %s -catalogue.gui.credits=致谢: %s -catalogue.gui.license=许可协议: %s -catalogue.gui.beta=此版本为 Beta 版 +catalogue.gui.mod_list=模组 +catalogue.gui.search=搜索 +catalogue.gui.config=配置 +catalogue.gui.open_mods_folder=打开模组文件夹 +catalogue.gui.licenses=许可协议:%s +catalogue.gui.authors=作者:%s +catalogue.gui.contributors=贡献者:%s +catalogue.gui.credits=致谢:%s +catalogue.gui.version=版本:%s +catalogue.gui.inner_version=内部版本:%s +catalogue.gui.beta=此版本为Beta版 catalogue.gui.ahead=此版本高于搜索到的最新版(%s) -catalogue.gui.update_available=更新(%s)可用: %s -catalogue.gui.update_available_no_page=更新(%s)可用 -catalogue.gui.beta_update_available=Beta 更新(%s)可用: %s -catalogue.gui.beta_update_available_no_page=Beta 更新(%s)可用 +catalogue.gui.update_available=更新(%s):%s +catalogue.gui.update_available_no_page=更新(%s) +catalogue.gui.beta_update_available=Beta更新(%s):%s +catalogue.gui.beta_update_available_no_page=Beta更新(%s) +catalogue.gui.info=此菜单由Catalogue提供。单击此处打开此模组的CurseForge页面! +catalogue.gui.favourite=标记为收藏 +catalogue.gui.remove_favourite=取消收藏 +catalogue.gui.options=选项 +catalogue.gui.filters=筛选 +catalogue.gui.filters.configs_only=仅显示有配置的模组 +catalogue.gui.filters.updates_only=仅显示有更新的模组 +catalogue.gui.filters.favourites=仅显示收藏 +catalogue.gui.sort=排序 +catalogue.gui.sort.alphabetically=A到Z +catalogue.gui.sort.alphabetically_reverse=Z到A +catalogue.gui.sort.favourites_first=收藏优先 +catalogue.gui.hide_libraries=隐藏库模组 +catalogue.gui.no_mods=没有模组…… catalogue.gui.mod_count=%s个模组 catalogue.gui.library_count=%s个库 +catalogue.gui.advanced_search.info=高级搜索查询将忽略任何激活的筛选器 +catalogue.gui.show_dependencies=显示前置模组 +catalogue.gui.show_dependents=显示依赖模组 +catalogue.gui.website=网站 +catalogue.gui.issue=提交Bug +catalogue.gui.mod_id=Mod ID:%s catalogue.config.library_list=库列表 +catalogue.config.library_list.tooltip=库模组的Mod ID列表。\n它们会在模组列表中显示灰色名字。 +catalogue.config.ignored_dependencies_list=忽略前置列表 +catalogue.config.ignored_dependencies_list.tooltip=忽略的前置模组的Mod ID列表。\n它们不会在搜索前置/依赖模组时显示。 +catalogue.config.enable_banner_limit=启用横幅限制 +catalogue.config.enable_banner_limit.tooltip=控制是否限制横幅尺寸。 +catalogue.config.banner_max_width=横幅最大宽度 +catalogue.config.banner_max_width.tooltip=横幅的最大宽度。当横幅限制被禁用时不会生效。 +catalogue.config.banner_max_height=横幅最大长度 +catalogue.config.banner_max_height.tooltip=横幅的最大长度。当横幅限制被禁用时不会生效。 diff --git a/src/main/resources/assets/catalogue/textures/gui/icons.png b/src/main/resources/assets/catalogue/textures/gui/icons.png index 89b1a035ef9383885d7ce7aa6bef872405b90b8b..784bb0d40f24de8ee0de7fc6154c7ac7c00cd418 100644 GIT binary patch delta 943 zcmV;g15o_G2E_-EB!2{FK}|sb0I`n?{9y$E000SaNLh0L01m+b01m+cxRGn^0000P zbVXQnQ*UN;cVTj60B~VxZgehgWpp4kE-)@KFoq{t0{{R6LP)xJMG!x_v?g3*6@pwLRxt>N79J^rgcSDSP=E2@z)DgG30NqCkVXU{ zhKTqF$aUlWCi^UNvy;tUq1od7!NblM*?pdQXL8wSG^(wBzhBaL+#MVoxS*e1Z1-K2 z!oMl~`PO#7hiBbrJ1fS7bFNZ#)<`8bpWSh>hQr~+$4T?^^Iqy>d@vZe)z#Hv4uiKI zy?0B`za+(+LVwtNdflP@Ugov+;82ejE_vmj=-*Q%)}+j)R06}J{a0Q;Kd+U-KpPKR z$z2<3A^_T$!|2rym%T8IX5L;X#=-#Jy&ux!ri7V^R!VI!1$TeG)Evdd7LI7fY`hOz&y`5fY7|ozJxg$J<~hoMF8{_ zLL5N6`;1L<1Y(27X@WQa?YtCM056pJ{dPP1I5(Hg&dwIXoT>oPCIEs^DAQIz9a2V9 zDxo_dWq&-n1H5AMYpqsM8At(yyH7tH3j@%Ohr7>nnj^rPl-ZO@V6KjMQK!?XG>;TO zmBIkH@nv56um1&N14k)*VQM}U2CH;)c*C#5WR>zyOd1JQXN`0$yej} zV}5;o-HW!yS%XpSI_CBBmA1xN6Pmx<{ZRx!I}9Oz{mFwl?Yhi|A*dQKr(Ku%Fa%YD z0>E%ejSFCLanbK`Y+hoHHE$LUcR4mMF~^!P0HF#{0HIg8#T)xa-eyJnC7;~(_945mmi0^<#1)w`XrZF5=LwpCwG={@! z&>es|Re{V)j4{`!065>FdHV6apgSNmPd~mF=#TMLpb#YbGS8aEAj48VTpE{QDPNYw zP&_hImy_7H2m=Y~4DlnriEF>G&z*Z?* z+0JyEm1%=lu;cDH^-U(P|wP7tRGJ{)5>;`~%M9taJ=kpmg zH8tqEK2taZgF%g)9Y(VFq`(=Z>3S$Ii03yqKIjZdgxZGRhO5){J-l%Uxie0hucukEqPEzyL@s{%btW zi@`y3T{n|T4&4MWSHEigD#Fnzwnf|c#PO^FjDHRL$uFuhcR}j+o(o-==2pbda9Q(3VXvxi{s>rN_C`i%|y>3!X~h*B0uTj)i|=kuBO z1&6~i@Beejp_}twgeuN>wGR7zE;a3&H*Y!a0YV5Ngb+dqA%qY@2qASDdL%PYk+P?Y zV~9p@a)c|#ljqNyMMNL59z1w(ao38K91^b_xa2wb*cgu7I_i~KemxeboWax8&t;uc GLK6T(PAu&J literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/catalogue/textures/gui/sprites/dropdown/item_highlighted.png b/src/main/resources/assets/catalogue/textures/gui/sprites/dropdown/item_highlighted.png new file mode 100644 index 0000000000000000000000000000000000000000..3abe659b6836dc1e34334fb0852800968f1613dc GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1SD^YpWXnZSkfJR9T^xl_H+M9WCij$3p^r= z85p<)K$vl@^4?OQpk#?_L`iUdT1k0gQ7S`0VrE{6US4X6f{C7io{>SDdL%PYk-Ddg zV~9p@@{Q6b_H`k5cQG$nv9Ow7PGp`%d5EB?D5JBi$3y4NV#ba~tYu9;`sP3_44$rj JF6*2UngG$8ELs2n literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/catalogue/textures/gui/sprites/widget/button.png b/src/main/resources/assets/catalogue/textures/gui/sprites/widget/button.png new file mode 100644 index 0000000000000000000000000000000000000000..96f6e3d00d0b011db496701c7fbe8319601a3bd5 GIT binary patch literal 4022 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 W-J)+A&suQ$c(yXPH^Z8ErThy34x9M^ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/catalogue/textures/gui/sprites/widget/button_disabled.png b/src/main/resources/assets/catalogue/textures/gui/sprites/widget/button_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..0530253715586d1577505071899fa06e2c888f9e GIT binary patch literal 2959 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/catalogue/textures/gui/sprites/widget/button_highlighted.png b/src/main/resources/assets/catalogue/textures/gui/sprites/widget/button_highlighted.png new file mode 100644 index 0000000000000000000000000000000000000000..ba1a7fe61c5cf64874dde6ccad4b3296f41830a9 GIT binary patch literal 6156 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 Date: Fri, 3 Oct 2025 20:34:36 +0800 Subject: [PATCH 50/91] Fix filter logic, add config to disable icon limit --- .../cleanroommc/catalogue/CatalogueConfig.java | 15 +++++++++++++++ .../cleanroommc/catalogue/client/Branding.java | 2 +- .../client/screen/CatalogueModListScreen.java | 2 +- .../platform/CleanroomPlatformHelper.java | 10 ++++++++++ .../platform/services/IPlatformHelper.java | 4 ++++ .../resources/assets/catalogue/lang/en_au.lang | 4 ++++ .../resources/assets/catalogue/lang/en_us.lang | 4 ++++ .../resources/assets/catalogue/lang/zh_cn.lang | 4 ++++ 8 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index d823cf401..f424d899b 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -64,6 +64,21 @@ public class CatalogueConfig { @Config.RangeInt(min = 0) public static int bannerMaxHeight = 256; + @Config.RequiresMcRestart + @Config.Comment({ + "Whether limit the size of mods' icons." + }) + @Config.LangKey("catalogue.config.enable_icon_limit") + public static boolean enableIconLimit = false; + + @Config.RequiresMcRestart + @Config.Comment({ + "The maximum of icon's width and height. Will not work if Enable Icon Limit is set false." + }) + @Config.LangKey("catalogue.config.icon_max_width_height") + @Config.RangeInt(min = 0) + public static int iconMaxWidthHeight = 256; + @SubscribeEvent public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) { if (event.getModID().equals(CatalogueConstants.MOD_ID)) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/Branding.java b/src/main/java/com/cleanroommc/catalogue/client/Branding.java index dd1b52407..835a6764b 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/Branding.java +++ b/src/main/java/com/cleanroommc/catalogue/client/Branding.java @@ -22,7 +22,7 @@ public record Branding(String prefix, int imageWidth, int imageHeight, BiPredicate predicate, Function locator, boolean override) { - public static final Branding ICON = new Branding("icon", 256, 256, ImagePredicate.SQUARE.and(ImagePredicate.LESS_THAN_OR_EQUAL), IModData::getImageIcon, false); + public static final Branding ICON = new Branding("icon", ClientServices.PLATFORM.getIconLimit(), ClientServices.PLATFORM.getIconLimit(), ImagePredicate.SQUARE.and(ClientServices.PLATFORM.getEnableIconLimit() ? ImagePredicate.LESS_THAN_OR_EQUAL : ImagePredicate.ANY), IModData::getImageIcon, false); public static final Branding BANNER = new Branding("banner", ClientServices.PLATFORM.getBannerLimit().maxWidth(), ClientServices.PLATFORM.getBannerLimit().maxHeight(), ClientServices.PLATFORM.getEnableBannerLimit() ? ImagePredicate.LESS_THAN_OR_EQUAL : ImagePredicate.ANY, IModData::getBanner, false); public static final Branding BACKGROUND = new Branding("background", 512, 256, ImagePredicate.EQUAL, IModData::getBackground, true); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 90d472766..837fcf75a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -444,7 +444,7 @@ private class ModList extends CatalogueListExtended { if (OPTION_CONFIGS_ONLY.booleanValue() && !data.hasConfig()) { return false; } - if (OPTION_UPDATES_ONLY.booleanValue() && (data.getUpdate() == null || data.getUpdate().updatable())) { + if (OPTION_UPDATES_ONLY.booleanValue() && (data.getUpdate() == null || !data.getUpdate().updatable())) { return false; } if (OPTION_HIDE_LIBRARIES.booleanValue() && data.isLibrary()) { diff --git a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java index 5877bbe64..f04f8389e 100644 --- a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java @@ -56,4 +56,14 @@ public boolean getEnableBannerLimit() { public Branding.BannerLimit getBannerLimit() { return new Branding.BannerLimit(CatalogueConfig.bannerMaxWidth, CatalogueConfig.bannerMaxHeight); } + + @Override + public boolean getEnableIconLimit() { + return CatalogueConfig.enableIconLimit; + } + + @Override + public int getIconLimit() { + return CatalogueConfig.iconMaxWidthHeight; + } } diff --git a/src/main/java/com/cleanroommc/catalogue/platform/services/IPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/services/IPlatformHelper.java index 79fffb2da..273f72153 100644 --- a/src/main/java/com/cleanroommc/catalogue/platform/services/IPlatformHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/platform/services/IPlatformHelper.java @@ -23,4 +23,8 @@ public interface IPlatformHelper { boolean getEnableBannerLimit(); Branding.BannerLimit getBannerLimit(); + + boolean getEnableIconLimit(); + + int getIconLimit(); } diff --git a/src/main/resources/assets/catalogue/lang/en_au.lang b/src/main/resources/assets/catalogue/lang/en_au.lang index 5646e584c..c5b743e48 100644 --- a/src/main/resources/assets/catalogue/lang/en_au.lang +++ b/src/main/resources/assets/catalogue/lang/en_au.lang @@ -48,3 +48,7 @@ catalogue.config.banner_max_width=Banner Max Width catalogue.config.banner_max_width.tooltip=The maximum of banner's width. Will not work if Enable Banner Limit is set false. catalogue.config.banner_max_height=Banner Max Height catalogue.config.banner_max_height.tooltip=The maximum of banner's height. Will not work if Enable Banner Limit is set false. +catalogue.config.enable_icon_limit=Enable Icon Limit +catalogue.config.enable_icon_limit.tooltip=Whether limit the size of mods' icons. +catalogue.config.icon_max_width_height=Icon Max Width/Height +catalogue.config.icon_max_width_height.tooltip=The maximum of icon's width and height. Will not work if Enable Icon Limit is set false. diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index cc03cf674..ed77d7f23 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -48,3 +48,7 @@ catalogue.config.banner_max_width=Banner Max Width catalogue.config.banner_max_width.tooltip=The maximum of banner's width. Will not work if Enable Banner Limit is false. catalogue.config.banner_max_height=Banner Max Height catalogue.config.banner_max_height.tooltip=The maximum of banner's height. Will not work if Enable Banner Limit is false. +catalogue.config.enable_icon_limit=Enable Icon Limit +catalogue.config.enable_icon_limit.tooltip=Whether limit the size of mods' icons. +catalogue.config.icon_max_width_height=Icon Max Width/Height +catalogue.config.icon_max_width_height.tooltip=The maximum of icon's width and height. Will not work if Enable Icon Limit is set false. diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index 082d1dfa0..4cfecd5d5 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -48,3 +48,7 @@ catalogue.config.banner_max_width=横幅最大宽度 catalogue.config.banner_max_width.tooltip=横幅的最大宽度。当横幅限制被禁用时不会生效。 catalogue.config.banner_max_height=横幅最大长度 catalogue.config.banner_max_height.tooltip=横幅的最大长度。当横幅限制被禁用时不会生效。 +catalogue.config.enable_icon_limit=启用图标限制 +catalogue.config.enable_icon_limit.tooltip=控制是否限制图标尺寸。 +catalogue.config.icon_max_width_height=图标最大长度/宽度 +catalogue.config.icon_max_width_height.tooltip=图标的最大长度/宽度。当图标限制被禁用时不会生效。 From b516706bdf08a7591292bc2659145232b9657918 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 12 Oct 2025 15:02:48 +0800 Subject: [PATCH 51/91] Fix scrolling logic, update translation --- .../client/screen/CatalogueModListScreen.java | 37 +++++------ .../screen/widget/CatalogueListExtended.java | 63 +++++++++++++------ .../assets/catalogue/lang/en_au.lang | 2 +- .../assets/catalogue/lang/en_us.lang | 2 +- .../assets/catalogue/lang/ru_ru.lang | 32 ++++++++++ .../assets/catalogue/lang/zh_cn.lang | 2 +- 6 files changed, 95 insertions(+), 43 deletions(-) create mode 100644 src/main/resources/assets/catalogue/lang/ru_ru.lang diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 837fcf75a..f4a00a49f 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -34,8 +34,9 @@ import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; -import org.lwjglx.input.Keyboard; import java.awt.*; import java.io.IOException; @@ -169,7 +170,7 @@ public int getWidth() { this.websiteButton = this.addButton(new CatalogueIconButton(4, contentLeft + buttonWidth + 5, 105, 20, 0, buttonWidth, I18n.format("catalogue.gui.website"))); this.websiteButton.visible = false; - this.issueButton = this.addButton(new CatalogueIconButton(5, contentLeft + buttonWidth + buttonWidth + 10, 105, 30, 0, buttonWidth, I18n.format("catalogue.gui.issue"))); + this.issueButton = this.addButton(new CatalogueIconButton(5, contentLeft + buttonWidth + buttonWidth + 10, 105, 30, 0, buttonWidth, I18n.format("catalogue.gui.submit_bug"))); this.issueButton.visible = false; this.descriptionList = new StringList(contentWidth + padding * 2, 50, contentLeft - padding, 130); @@ -379,6 +380,12 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti super.mouseClicked(mouseX, mouseY, button); } + @Override + protected void mouseReleased(int mouseX, int mouseY, int button) { + if (this.modList.mouseReleased(mouseX, mouseY, button)) return; + super.mouseReleased(mouseX, mouseY, button); + } + @Override protected void keyTyped(char typedChar, int key) throws IOException { if (isKeyComboCtrlF(key) && !this.searchTextField.isFocused()) { @@ -558,26 +565,19 @@ protected void drawContainerBackground(@NotNull Tessellator tessellator) { super.drawContainerBackground(tessellator); } - @Override - protected void drawTopAndBottom(Tessellator tessellator) { - } - @Override protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { } @Override - public boolean mouseClicked(int mouseX, int mouseY, int button) { - if (super.mouseClicked(mouseX, mouseY, button)) { - this.hideFavourites = button == 0 && mouseX >= this.getScrollBarX() && mouseY < this.getScrollBarX() + 6; - return true; - } - return false; + public void handleMouseInput() { + this.hideFavourites = Mouse.getEventDWheel() != 0; + super.handleMouseInput(); } @Override public boolean mouseReleased(int mouseX, int mouseY, int button) { - if (this.hideFavourites && button == 0) { + if (this.hideFavourites) { this.hideFavourites = false; } return super.mouseReleased(mouseX, mouseY, button); @@ -792,7 +792,6 @@ public IModData getData() { @Override public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { - if (this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) return false; if (mouseEvent == 1) { DropdownMenu menu = DropdownMenu.builder(CatalogueModListScreen.this) .setMinItemSize(0, 16) @@ -806,8 +805,8 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEven CatalogueModListScreen.this.searchTextField.setText(filter); }).build(); menu.toggle(mouseX, mouseY); - return false; - } else if (mouseEvent == 0) { + return true; + } else if (mouseEvent == 0 && !this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) { CatalogueModListScreen.this.setSelectedModData(this.data); this.list.setSelected(this); return true; @@ -845,7 +844,7 @@ public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTick) @Override public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { + if (super.mousePressed(mc, mouseX, mouseY) && !CatalogueModListScreen.this.modList.hideFavourites) { FAVOURITES.toggle(this.modId); ModListEntry.this.list.filterAndUpdateList(); this.playPressSound(mc.getSoundHandler()); @@ -1021,10 +1020,6 @@ public IGuiListEntry getListEntry(int index) { return this.entries.get(index); } - @Override - protected void drawTopAndBottom(Tessellator tessellator) { - } - @Override protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index c518a8732..dc6fe2ce5 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -57,9 +57,6 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { GlStateManager.shadeModel(GL11.GL_SMOOTH); GlStateManager.disableTexture2D(); - // Shadow the top and bottom - this.drawTopAndBottom(tessellator); - // Scroll Bar if (this.scrollBarVisible) { this.drawScrollBar(tessellator, maxScroll); @@ -74,22 +71,6 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { GlStateManager.disableBlend(); } - protected void drawTopAndBottom(Tessellator tessellator) { - BufferBuilder buffer = tessellator.getBuffer(); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); - buffer.pos(this.left, this.top + 4, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 0).endVertex(); - buffer.pos(this.right, this.top + 4, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 0).endVertex(); - buffer.pos(this.right, this.top, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos(this.left, this.top, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 255).endVertex(); - tessellator.draw(); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); - buffer.pos(this.left, this.bottom, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos(this.right, this.bottom, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos(this.right, this.bottom - 4, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 0).endVertex(); - buffer.pos(this.left, this.bottom - 4, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 0).endVertex(); - tessellator.draw(); - } - protected void drawScrollBar(Tessellator tessellator, int maxScroll) { BufferBuilder buffer = tessellator.getBuffer(); @@ -162,7 +143,51 @@ protected void drawSelectionBox(int mouseX, int mouseY, float partialTicks) { } @Deprecated + @Override protected void drawSelectionBox(int contentLeft, int contentTop, int mouseXIn, int mouseYIn, float partialTicks) { + this.drawSelectionBox(mouseX, mouseY, partialTicks); + } + + @Override + public boolean mouseClicked(int mouseX, int mouseY, int mouseEvent) { + if (this.isMouseYWithinSlotBounds(mouseY)) { + int slotIndex = this.getSlotIndexFromScreenCoords(mouseX, mouseY); + if (slotIndex >= 0) { + int j = this.left + this.getListLeft(); + int k = this.top + 4 - this.getAmountScrolled() + slotIndex * this.slotHeight + this.headerPadding; + int relativeX = mouseX - j; + int relativeY = mouseY - k; + if (this.getListEntry(slotIndex).mousePressed(slotIndex, mouseX, mouseY, mouseEvent, relativeX, relativeY)) { + this.setEnabled(false); + return true; + } + } + } + return false; + } + + @Override + public boolean mouseReleased(int x, int y, int mouseEvent) { + for (int slotIndex = 0; slotIndex < this.getSize(); ++slotIndex) { + int j = this.left + this.getListLeft(); + int k = this.top + 4 - this.getAmountScrolled() + slotIndex * this.slotHeight + this.headerPadding; + int relativeX = x - j; + int relativeY = y - k; + this.getListEntry(slotIndex).mouseReleased(slotIndex, x, y, mouseEvent, relativeX, relativeY); + } + this.setEnabled(true); + return false; + } + + @Override + public int getSlotIndexFromScreenCoords(int mouseX, int mouseY) { + int i = this.getListWidth() / 2; + int j = this.left + this.width / 2; + int k = j - i; + int l = j + i; + int i1 = MathHelper.floor(mouseY - this.top) - this.headerPadding + this.getAmountScrolled() - 4; + int j1 = i1 / this.slotHeight; + return mouseX < this.getScrollBarX() && mouseX >= k && mouseX <= l && j1 >= 0 && i1 >= 0 && j1 < this.getSize() ? j1 : -1; } public void setClampedAmountScrolled(float scroll) { diff --git a/src/main/resources/assets/catalogue/lang/en_au.lang b/src/main/resources/assets/catalogue/lang/en_au.lang index c5b743e48..80cac5269 100644 --- a/src/main/resources/assets/catalogue/lang/en_au.lang +++ b/src/main/resources/assets/catalogue/lang/en_au.lang @@ -35,7 +35,7 @@ catalogue.gui.advanced_search.info=Advanced search queries will ignore any activ catalogue.gui.show_dependencies=Show Dependencies catalogue.gui.show_dependents=Show Dependents catalogue.gui.website=Website -catalogue.gui.issue=Submit Bug +catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s catalogue.config.library_list=Library List diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index ed77d7f23..8e1d53c11 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -35,7 +35,7 @@ catalogue.gui.advanced_search.info=Advanced search queries will ignore any activ catalogue.gui.show_dependencies=Show Dependencies catalogue.gui.show_dependents=Show Dependents catalogue.gui.website=Website -catalogue.gui.issue=Submit Bug +catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s catalogue.config.library_list=Library List diff --git a/src/main/resources/assets/catalogue/lang/ru_ru.lang b/src/main/resources/assets/catalogue/lang/ru_ru.lang new file mode 100644 index 000000000..43a759b8b --- /dev/null +++ b/src/main/resources/assets/catalogue/lang/ru_ru.lang @@ -0,0 +1,32 @@ +catalogue.gui.no_selection=Нет выбранного мода… +catalogue.gui.mod_list=Моды +catalogue.gui.search=Поиск +catalogue.gui.config=Настройки +catalogue.gui.website=Веб-сайт +catalogue.gui.submit_bug=Сообщить баг +catalogue.gui.open_mods_folder=Открыть папку модов +catalogue.gui.licenses=Лицензия(и): %s +catalogue.gui.authors=Автор(ы): %s +catalogue.gui.contributors=Участник(ы): %s +catalogue.gui.credits=Приписание: %s +catalogue.gui.version=Версия: %s +catalogue.gui.update_available=Обновление: %s +catalogue.gui.info=Это меню предоставлено Catalogue. Нажмите, чтобы открыть страницу CurseForge для этого мода! +catalogue.gui.favourite=Отметить как избранное +catalogue.gui.remove_favourite=Удалить из избранного +catalogue.gui.options=Настройки +catalogue.gui.filters=Фильтры +catalogue.gui.filters.configs_only=Моды с конфигами +catalogue.gui.filters.updates_only=Моды с обновлениями +catalogue.gui.filters.favourites=Только избранное +catalogue.gui.sort=Сортировка +catalogue.gui.sort.alphabetically_reverse=Z до A +catalogue.gui.sort.alphabetically=A до Z +catalogue.gui.sort.favourites_first=Сначала избранное +catalogue.gui.hide_libraries=Скрыть библиотеки +catalogue.gui.no_mods=Нет модов... +catalogue.gui.mod_count=%s модов +catalogue.gui.library_count=%s библиотек +catalogue.gui.advanced_search.info=Расширенные поисковые запросы будут игнорировать все активные фильтры +catalogue.gui.show_dependencies=Показывать зависимости +catalogue.gui.show_dependents=Показывать зависимых diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index 4cfecd5d5..67a2ea03e 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -35,7 +35,7 @@ catalogue.gui.advanced_search.info=高级搜索查询将忽略任何激活的筛 catalogue.gui.show_dependencies=显示前置模组 catalogue.gui.show_dependents=显示依赖模组 catalogue.gui.website=网站 -catalogue.gui.issue=提交Bug +catalogue.gui.submit_bug=提交Bug catalogue.gui.mod_id=Mod ID:%s catalogue.config.library_list=库列表 From 24524737660349cbc3b37fb0d3652da4be0c402e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 19 Oct 2025 14:08:30 +0800 Subject: [PATCH 52/91] Optimize --- .../client/screen/CatalogueModListScreen.java | 29 +++++++++---------- .../platform/CleanroomPlatformHelper.java | 2 +- .../net/minecraftforge/fml/common/Loader.java | 8 +++++ 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index f4a00a49f..dc94a75e7 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -100,11 +100,11 @@ public class CatalogueModListScreen extends GuiScreen implements DropdownMenuHan private CatalogueIconButton configButton; private CatalogueIconButton websiteButton; private CatalogueIconButton issueButton; + private StringList descriptionList; + private @Nullable DropdownMenu menu; private List activeTooltip; private int tooltipYOffset; - private StringList descriptionList; - private @Nullable DropdownMenu menu; private long lastClickTime; @@ -195,7 +195,7 @@ public int getWidth() { public void actionPerformed(GuiButton button) { switch (button.id) { case 1: - this.mc.displayGuiScreen(parentScreen); + this.mc.displayGuiScreen(this.parentScreen); break; case 2: try { @@ -593,16 +593,13 @@ public boolean shouldHideFavourites() { } private static boolean performSearchFilter(String query, IModData data) { - if (!query.startsWith("@")) - return false; + if (!query.startsWith("@")) return false; int end = query.indexOf(":"); - if (end == -1) - return false; + if (end == -1) return false; String type = query.substring(1, end).toLowerCase(Locale.ENGLISH); - if (!SEARCH_FILTERS.containsKey(type)) - return false; + if (!SEARCH_FILTERS.containsKey(type)) return false; String value = query.substring(end + 1); return SEARCH_FILTERS.get(type).predicate().test(value, data); @@ -687,7 +684,7 @@ private void drawIcon(int top, int left) { if (iconInfo != null) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); - mc.getTextureManager().bindTexture(iconInfo.resource()); + CatalogueModListScreen.this.mc.getTextureManager().bindTexture(iconInfo.resource()); drawScaledCustomSizeModalRect(left + 4, top + 3, 0.0F, 0.0F, iconInfo.width(), iconInfo.height(), 16, 16, iconInfo.width(), iconInfo.height()); GlStateManager.disableBlend(); return; @@ -747,7 +744,7 @@ private ItemStack getItemIcon() { if (optional.isPresent()) { ItemStack item = optional.get(); if (!item.isEmpty()) { - // If the item is in a creative tab, Catalogue will use the tab's icon + // If the item is in a creative tab, Catalogue will attempt to use the tab's icon if (item.getItem().getCreativeTab() != null) { try { ItemStack tabItem = item.getItem().getCreativeTab().getIcon(); @@ -806,7 +803,8 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEven }).build(); menu.toggle(mouseX, mouseY); return true; - } else if (mouseEvent == 0 && !this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) { + } else if (mouseEvent == 0) { + if (this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) return true; CatalogueModListScreen.this.setSelectedModData(this.data); this.list.setSelected(this); return true; @@ -848,6 +846,7 @@ public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { FAVOURITES.toggle(this.modId); ModListEntry.this.list.filterAndUpdateList(); this.playPressSound(mc.getSoundHandler()); + CatalogueModListScreen.this.updateSelectedModList(); return true; } return false; @@ -1067,8 +1066,7 @@ public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relat @SuppressWarnings("SameParameterValue") private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { String formatted = I18n.format(format, text); // Attempting to keep Forge's lang since it's already support many languages - String colon = ":"; - if (formatted.contains(":")) colon = ":"; + String colon = formatted.contains(":") ? ":" : ":"; String label = formatted.substring(0, formatted.indexOf(colon) + 1); String content = formatted.substring(formatted.indexOf(colon) + 1); if (this.fontRenderer.getStringWidth(formatted) > maxWidth) { @@ -1387,8 +1385,7 @@ private void load() { } private void save() { - if (!this.needsSave) - return; + if (!this.needsSave) return; try { this.needsSave = false; diff --git a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java index f04f8389e..50687d135 100644 --- a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java @@ -28,7 +28,7 @@ public List getAllModData() { @Override public File getModDirectory() { - return Loader.instance().getConfigDir().getParentFile().toPath().resolve("mods").toFile(); + return Loader.instance().getModDir(); } @Override diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index 47a03891d..ae56745e7 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -173,6 +173,9 @@ public class Loader * The canonical configuration directory */ private File canonicalConfigDir; + /** + * The canonical mods directory + */ private File canonicalModsDir; private LoadController modController; private MinecraftDummyContainer minecraft; @@ -705,6 +708,11 @@ public File getConfigDir() return canonicalConfigDir; } + public File getModDir() + { + return canonicalModsDir; + } + public String getCrashInformation() { // Handle being called before we've begun setup From 2dc389022ac4082c09bf47a9150f8da7c9acd3ca Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 19 Oct 2025 14:57:24 +0800 Subject: [PATCH 53/91] Update DropdownMenu.java --- .../client/screen/widget/DropdownMenu.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java index 219ca7656..9c1fb6208 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java @@ -40,10 +40,10 @@ public class DropdownMenu extends Gui implements LayoutElement { public boolean active; public boolean visible; - public int x; - public int y; - public int width; - public int height; + private int x; + private int y; + private int width; + private int height; private DropdownMenu(DropdownMenuHandler handler) { super(); @@ -114,7 +114,7 @@ private void deepClose() { public void drawScreen(Minecraft minecraft, int mouseX, int mouseY, float deltaTick) { GlStateManager.pushMatrix(); drawRect(0, 0, minecraft.displayWidth, minecraft.displayHeight, 0x50000000); - drawRect(this.x, this.y, this.x + this.width, this.y + this.height, 0xAA000000); + drawRect(this.getX(), this.getY(), this.getX() + this.getWidth(), this.getY() + this.getHeight(), 0xAA000000); this.items.forEach(widget -> widget.drawWidget(minecraft, mouseX, mouseY, deltaTick)); if (this.subMenu != null) { this.subMenu.drawScreen(minecraft, mouseX, mouseY, deltaTick); @@ -137,6 +137,7 @@ public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY) { return clicked.get(); } + @Override public void visitWidgets(Consumer consumer) { this.layout.visitWidgets(consumer); } @@ -250,6 +251,19 @@ public int getHeight() { return this.height; } + public void setWidth(int width) { + this.width = width; + } + + public void setHeight(int height) { + this.height = height; + } + + public void setSize(int width, int height) { + this.setWidth(width); + this.setHeight(height); + } + @Override public void setPosition(int pX, int pY) { LayoutElement.super.setPosition(pX, pY); @@ -448,8 +462,7 @@ public Builder addMenu(String label, Builder builder) { public DropdownMenu build() { int maxWidth = this.items.stream().mapToInt(MenuItem::calculateWidth).max().orElse(100); this.items.forEach(widget -> { - widget.width = Math.max(maxWidth, this.minItemWidth); - widget.height = this.minItemHeight; + widget.setSize(Math.max(maxWidth, this.minItemWidth), this.minItemHeight); this.base.addItem(widget); }); return this.base; From e3893a661e4544f5d08cf25e7b7a0a93673d71f3 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 1 Nov 2025 18:30:01 +0800 Subject: [PATCH 54/91] Sync changes, apply some IDE suggestions --- .../catalogue/CatalogueConfig.java | 5 +- .../catalogue/client/Branding.java | 2 +- .../catalogue/client/CleanroomModData.java | 74 ++- .../catalogue/client/ClientHandler.java | 25 + .../catalogue/client/ClientHelper.java | 4 - .../catalogue/client/IModData.java | 25 +- .../catalogue/client/ImagePredicate.java | 3 +- .../client/screen/CatalogueModListScreen.java | 193 +++--- .../client/screen/MinecraftModData.java | 57 +- .../screen/widget/CatalogueListExtended.java | 7 +- .../screen/widget/CatalogueTextButton.java | 4 +- .../screen/widget/CatalogueTextField.java | 38 +- .../platform/CleanroomPlatformHelper.java | 82 ++- .../common/CatalogueContainer.java | 2 + .../fml/client/GuiErrorBase.java | 3 +- .../minecraftforge/fml/client/GuiModList.java | 627 +++++++++++++++++- .../fml/client/GuiSlotModList.java | 132 ++++ .../net/minecraftforge/fml/common/Loader.java | 2 +- .../fml/common/MetadataCollection.java | 6 +- .../resources/assets/forge/lang/en_us.lang | 2 + .../resources/assets/forge/lang/zh_cn.lang | 2 + 21 files changed, 1084 insertions(+), 211 deletions(-) create mode 100644 src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java create mode 100644 src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index f424d899b..c235da4b3 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -3,11 +3,10 @@ import net.minecraftforge.common.config.Config; import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.fml.client.event.ConfigChangedEvent; -import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.jetbrains.annotations.NotNull; @Config(modid = CatalogueConstants.MOD_ID) -@Mod.EventBusSubscriber(modid = CatalogueConstants.MOD_ID) public class CatalogueConfig { @Config.RequiresMcRestart @Config.Comment({ @@ -80,7 +79,7 @@ public class CatalogueConfig { public static int iconMaxWidthHeight = 256; @SubscribeEvent - public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) { + public static void onConfigChanged(@NotNull ConfigChangedEvent.OnConfigChangedEvent event) { if (event.getModID().equals(CatalogueConstants.MOD_ID)) { ConfigManager.sync(CatalogueConstants.MOD_ID, Config.Type.INSTANCE); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/Branding.java b/src/main/java/com/cleanroommc/catalogue/client/Branding.java index 835a6764b..590a5b16a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/Branding.java +++ b/src/main/java/com/cleanroommc/catalogue/client/Branding.java @@ -28,7 +28,7 @@ public record Branding(String prefix, int imageWidth, int imageHeight, public Optional loadResource(IModData data) { String resource = this.locator.apply(data); - if (resource.isBlank()) return Optional.empty(); + if (resource == null || resource.isBlank()) return Optional.empty(); String modId = data.getModId(); BufferedImage image = null; diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index b1426ef61..196785f4d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -58,77 +58,88 @@ public String getDisplayName() { } @Override - public @NotNull String getVersion() { + public String getVersion() { return this.info.getDisplayVersion(); } @Override - public @NotNull String getInnerVersion() { + public String getInnerVersion() { return this.info.getVersion(); } + @Nullable @Override - public @NotNull String getDescription() { + public String getDescription() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.description : ""; + return metadata != null ? metadata.description : null; } + @Nullable @Override - public @NotNull String getItemIcon() { + public String getItemIcon() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.iconItem : ""; + return metadata != null ? metadata.iconItem : null; } + @Nullable @Override - public @NotNull String getImageIcon() { + public String getImageIcon() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.iconFile : ""; + return metadata != null ? metadata.iconFile : null; } + @Nullable @Override - public @NotNull String getLicense() { + public String getLicense() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.license : ""; + return metadata != null ? metadata.license : null; } + @Nullable @Override - public @NotNull String getCredits() { + public String getCredits() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.credits : ""; + return metadata != null ? metadata.credits : null; } + @Nullable @Override - public @NotNull String getAuthors() { + public String getAuthors() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.getAuthorList() : ""; + return metadata != null ? metadata.getAuthorList() : null; } + @Nullable @Override - public @NotNull String getHomepage() { + public String getHomepage() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.url : ""; + return metadata != null ? metadata.url : null; } + @Nullable @Override - public @NotNull String getIssueTracker() { + public String getIssueTracker() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.issueTrackerUrl : ""; + return metadata != null ? metadata.issueTrackerUrl : null; } + @Nullable @Override - public @NotNull String getBanner() { + public String getBanner() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.logoFile : ""; + return metadata != null ? metadata.logoFile : null; } + @Nullable @Override - public @NotNull String getBackground() { + public String getBackground() { ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.backgroundFile : ""; + return metadata != null ? metadata.backgroundFile : null; } + @Nullable @Override - public @Nullable Update getUpdate() { + public Update getUpdate() { ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.info); if (result != null && result.status.shouldDraw()) { return new Update(result.status.isAnimated(), result.url, result.status.getSheetOffset(), VERSION_CHECK_ICONS, result.status == ForgeVersion.Status.OUTDATED || result.status == ForgeVersion.Status.BETA_OUTDATED, result.latestFound, result.homepage); @@ -173,10 +184,11 @@ public void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y) { Gui.drawModalRectWithCustomSizedTexture(x, y, update.texOffset() * 8, vOffset, 8, 8, 64, 16); } + @Nullable @Override - public @NotNull String getUpdateText(Update update) { + public String getUpdateText(Update update) { ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.info); - if (result == null) return ""; + if (result == null) return null; switch (result.status) { case BETA: return TextFormatting.GOLD + I18n.format("catalogue.gui.beta"); @@ -195,20 +207,22 @@ public void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y) { return TextFormatting.GREEN + I18n.format("catalogue.gui.update_available_no_page", update.latestFound()); } } - return ""; + return null; } + @Nullable @Override - public @Nullable IResourcePack getResourcePack() { + public IResourcePack getResourcePack() { return FMLClientHandler.instance().getResourcePackFor(this.getModId()); } - private @Nullable ModMetadata getMetadata() { + @Nullable + private ModMetadata getMetadata() { ModMetadata metadata = this.info.getMetadata(); return metadata != null && !metadata.autogenerated ? metadata : null; } - private Type analyzeType(ModContainer info) { + private Type analyzeType(@NotNull ModContainer info) { if (LIB_MODS.contains(info.getModId())) { return Type.LIBRARY; } else { @@ -216,7 +230,7 @@ private Type analyzeType(ModContainer info) { } } - private Set analyzeDependencies(ModContainer source) { + private Set analyzeDependencies(@NotNull ModContainer source) { List versions = source.getDependencies(); return versions.stream() .map(ArtifactVersion::getLabel) diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java new file mode 100644 index 000000000..d08439edf --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java @@ -0,0 +1,25 @@ +package com.cleanroommc.catalogue.client; + +import com.cleanroommc.catalogue.CatalogueConstants; +import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; +import net.minecraft.client.Minecraft; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.common.config.Config; +import net.minecraftforge.common.config.ConfigManager; +import net.minecraftforge.fml.client.GuiModList; +import net.minecraftforge.fml.client.event.ConfigChangedEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Author: MrCrayfish + */ +public class ClientHandler { + @SubscribeEvent + public static void onOpenScreen(@NotNull GuiOpenEvent event) { + //noinspection deprecation + if (event.getGui() instanceof GuiModList) { + event.setGui(new CatalogueModListScreen(Minecraft.getMinecraft().currentScreen)); + } + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java index 9a551a009..cd1f66461 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java @@ -37,10 +37,6 @@ public static boolean isMouseWithin(int x, int y, int width, int height, int mou return mouseX >= x && mouseX < x + width && mouseY >= y && mouseY < y + height; } - public static boolean isPlayingGame() { - return Minecraft.getMinecraft().player != null; - } - public static void blitNineSlicedSprite(NineSlice nineSlice, int x, int y, int width, int height) { blitNineSlicedSprite(nineSlice, x, y, 0, width, height); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/IModData.java b/src/main/java/com/cleanroommc/catalogue/client/IModData.java index 6f4dc7906..737e80f13 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/IModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/IModData.java @@ -5,7 +5,6 @@ import net.minecraft.client.resources.IResourcePack; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.TextFormatting; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Set; @@ -20,40 +19,38 @@ public interface IModData { String getDisplayName(); - @NotNull String getVersion(); - @NotNull String getInnerVersion(); - @NotNull + @Nullable String getDescription(); - @NotNull + @Nullable String getItemIcon(); - @NotNull + @Nullable String getImageIcon(); - @NotNull + @Nullable String getLicense(); - @NotNull + @Nullable String getCredits(); - @NotNull + @Nullable String getAuthors(); - @NotNull + @Nullable String getHomepage(); - @NotNull + @Nullable String getIssueTracker(); - @NotNull + @Nullable String getBanner(); - @NotNull + @Nullable String getBackground(); @Nullable @@ -72,7 +69,7 @@ public interface IModData { void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y); - @NotNull + @Nullable String getUpdateText(Update update); record Update(boolean animated, String url, int texOffset, ResourceLocation textures, boolean updatable, diff --git a/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java b/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java index 5aeea63ce..6500d976d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java @@ -2,6 +2,7 @@ import com.cleanroommc.catalogue.exception.InvalidBrandingImageException; import org.apache.commons.lang3.function.TriFunction; +import org.jetbrains.annotations.Nullable; import java.awt.image.BufferedImage; import java.util.Objects; @@ -19,7 +20,7 @@ public record ImagePredicate(TriFunction true, (maxWidth, maxHeight) -> ""); @Override - public boolean test(BufferedImage image, Branding branding) throws InvalidBrandingImageException { + public boolean test(@Nullable BufferedImage image, Branding branding) throws InvalidBrandingImageException { if (image == null) { throw new InvalidBrandingImageException("Image is null"); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index dc94a75e7..40dc0171d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -88,25 +88,28 @@ public class CatalogueModListScreen extends GuiScreen implements DropdownMenuHan })).build(); private static final TextFormatting SEARCH_FILTER_KEY = TextFormatting.GOLD; private static final TextFormatting SEARCH_FILTER_VALUE = TextFormatting.WHITE; - private static ImageInfo cachedBackground; + private static @Nullable ImageInfo cachedBackground; private static boolean loaded = false; private final GuiScreen parentScreen; - private CatalogueTextButton optionsButton; private CatalogueTextField searchTextField; private ModList modList; + private StringList descriptionList; private IModData selectedModData; + private CatalogueTextButton optionsButton; private CatalogueIconButton modFolderButton; private CatalogueIconButton configButton; private CatalogueIconButton websiteButton; private CatalogueIconButton issueButton; - private StringList descriptionList; private @Nullable DropdownMenu menu; - private List activeTooltip; + private @Nullable List activeTooltip; private int tooltipYOffset; - + /** + * Time record of text box clicking. + */ private long lastClickTime; + private boolean didRepeatEvents; public CatalogueModListScreen(GuiScreen parent) { super(); @@ -131,7 +134,8 @@ public void setMenu(@Nullable DropdownMenu menu) { @Override public void initGui() { - super.initGui(); + this.didRepeatEvents = Keyboard.areRepeatEventsEnabled(); + Keyboard.enableRepeatEvents(true); this.searchTextField = new CatalogueTextField(0, this.fontRenderer, 11, 25, 148, 20) { @Override public int getWidth() { @@ -149,7 +153,6 @@ public int getWidth() { OPTION_QUERY.setValue(s); this.updateSearchFieldSuggestion(s); this.modList.filterAndUpdateList(); - this.updateSelectedModList(); } }); @@ -182,8 +185,7 @@ public int getWidth() { // Resizing window causes all widgets to be recreated, therefore need to update selected info if (this.selectedModData != null) { this.setSelectedModData(this.selectedModData); - this.updateSelectedModList(); - ModListEntry entry = this.modList.getEntryFromInfo(this.selectedModData); + ModListEntry entry = this.modList.getSelected(); if (entry != null) { this.modList.centerScrollOn(entry); } @@ -192,7 +194,13 @@ public int getWidth() { } @Override - public void actionPerformed(GuiButton button) { + public void onGuiClosed() { + Keyboard.enableRepeatEvents(this.didRepeatEvents); + FAVOURITES.save(); + } + + @Override + public void actionPerformed(@NotNull GuiButton button) { switch (button.id) { case 1: this.mc.displayGuiScreen(this.parentScreen); @@ -205,9 +213,7 @@ public void actionPerformed(GuiButton button) { } break; case 3: - if (this.selectedModData != null) { - this.selectedModData.openConfigScreen(this.mc, this); - } + this.selectedModData.openConfigScreen(this.mc, this); break; case 4: this.openLink(this.selectedModData.getHomepage()); @@ -255,6 +261,8 @@ public void actionPerformed(GuiButton button) { }).build(); menu.toggle(button); break; + default: + break; } } @@ -365,11 +373,11 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti } // Left click to apply suggestions if (button == 0) { - String text = this.searchTextField.getText(); long currentTine = Minecraft.getSystemTime(); - if (!text.isEmpty() && currentTine - this.lastClickTime < 250L && !this.searchTextField.getIsTextTruncated()) { - text += this.searchTextField.getSuggestion(); - this.searchTextField.setText(text); + String text = this.searchTextField.getText(); + String suggestion = this.searchTextField.getSuggestion(); + if (!text.isEmpty() && !this.searchTextField.isTextTruncated() && !suggestion.isEmpty() && currentTine - this.lastClickTime < 250L) { + this.searchTextField.setText(text + suggestion); this.lastClickTime = currentTine; return; } @@ -392,6 +400,14 @@ protected void keyTyped(char typedChar, int key) throws IOException { this.searchTextField.setFocused(true); return; } + if (key == Keyboard.KEY_TAB && this.searchTextField.isFocused()) { + String text = this.searchTextField.getText(); + String suggestion = this.searchTextField.getSuggestion(); + if (!text.isEmpty() && !this.searchTextField.isTextTruncated() && !suggestion.isEmpty()) { + this.searchTextField.setText(text + suggestion); + return; + } + } if (this.searchTextField.textboxKeyTyped(typedChar, key)) return; super.keyTyped(typedChar, key); } @@ -457,14 +473,15 @@ private class ModList extends CatalogueListExtended { if (OPTION_HIDE_LIBRARIES.booleanValue() && data.isLibrary()) { return false; } + //noinspection RedundantIfStatement if (OPTION_FAVOURITES_ONLY.booleanValue() && !FAVOURITES.has(data.getModId())) { return false; } return true; }; + private final List children = new ArrayList<>(); + private @Nullable ModListEntry selected; private boolean hideFavourites; - private List children = Lists.newArrayList(); - private ModListEntry selected; public ModList() { super(CatalogueModListScreen.this.mc, 150, CatalogueModListScreen.this.height, 46, CatalogueModListScreen.this.height - 35, 26); @@ -492,20 +509,20 @@ public void filterAndUpdateList() { .sorted(OPTION_SORT.getValue()) .collect(Collectors.toList()); this.replaceEntries(entries); + if (CatalogueModListScreen.this.selectedModData != null) { + Optional selectedEntry = this.children.stream().filter(entry -> entry.data == CatalogueModListScreen.this.selectedModData).findFirst(); + selectedEntry.ifPresent(this::setSelected); + } this.clampAmountScrolled(); } - public ModListEntry getEntryFromInfo(IModData data) { - return this.children.stream().filter(entry -> entry.data == data).findFirst().orElse(null); - } - - protected void centerScrollOn(ModListEntry pEntry) { + public void centerScrollOn(ModListEntry pEntry) { this.setAmountScrolled((float) (this.children.indexOf(pEntry) * this.slotHeight + this.slotHeight / 2 - (this.bottom - this.top) / 2)); } protected void clearEntries() { this.children.clear(); - this.selected = null; + this.setSelected(null); } protected void replaceEntries(Collection entries) { @@ -528,11 +545,6 @@ public int getListLeft() { return this.left; } - @Override - public int getListRight() { - return this.getListLeft() + this.getListWidth(); - } - @Override public int getListWidth() { return this.width - (this.isScrollBarVisible() ? 6 : 0); @@ -543,7 +555,7 @@ protected int getSize() { return this.children.size(); } - public ModListEntry getSelected() { + public @Nullable ModListEntry getSelected() { return this.selected; } @@ -558,7 +570,7 @@ protected boolean isSelected(int slotIndex) { @Override protected void drawContainerBackground(@NotNull Tessellator tessellator) { - if (ClientHelper.isPlayingGame()) { + if (this.mc.world != null) { drawRect(this.left, this.top, this.right, this.bottom, 0x66000000); return; } @@ -583,16 +595,12 @@ public boolean mouseReleased(int mouseX, int mouseY, int button) { return super.mouseReleased(mouseX, mouseY, button); } - public boolean isMouseOver() { - return ClientHelper.isMouseWithin(this.getListLeft(), this.top, this.width, this.bottom - this.top, this.mouseX, this.mouseY); - } - public boolean shouldHideFavourites() { return this.hideFavourites; } } - private static boolean performSearchFilter(String query, IModData data) { + private static boolean performSearchFilter(@NotNull String query, IModData data) { if (!query.startsWith("@")) return false; int end = query.indexOf(":"); @@ -638,8 +646,9 @@ private class ModListEntry implements CatalogueListExtended.IGuiListEntry { private final ModList list; private final PinnedButton button; private ItemStack icon; + private boolean hovered; - public ModListEntry(IModData data, ModList list) { + public ModListEntry(@NotNull IModData data, @NotNull ModList list) { this.data = data; this.list = list; this.button = new PinnedButton(data.getModId()); @@ -648,11 +657,12 @@ public ModListEntry(IModData data, ModList list) { @Override public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { + this.hovered = hovered; // Draws mod name and version boolean inOptionsMenu = CatalogueModListScreen.this.menu != null; boolean drawFavouriteIcon = !inOptionsMenu && !this.list.shouldHideFavourites() && ClientHelper.isMouseWithin(left + rowWidth - rowHeight - 4, top, rowHeight + 4, rowHeight, mouseX, mouseY) || FAVOURITES.has(this.data.getModId()); drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModName(drawFavouriteIcon), left + 24, top + 2, 0xFFFFFF); - drawString(CatalogueModListScreen.this.fontRenderer, TextFormatting.GRAY + this.data.getVersion(), left + 24, top + 12, 0xFFFFFF); + drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModVersion(drawFavouriteIcon), left + 24, top + 12, 0xFFFFFF); // Draw image icon or fallback to item icon this.drawIcon(top, left); @@ -661,7 +671,7 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, IModData.Update update = this.data.getUpdate(); if (update != null) { int iconLeft = left + rowWidth - 8 - 9 + (drawFavouriteIcon ? -14 : 0); - this.data.drawUpdateIcon(mc, update, iconLeft, top + 7); + this.data.drawUpdateIcon(CatalogueModListScreen.this.mc, update, iconLeft, top + 7); } if (drawFavouriteIcon) { @@ -703,7 +713,7 @@ private void drawIcon(int top, int left) { } } - private ItemStack getItemIcon() { + private @NotNull ItemStack getItemIcon() { if (ITEM_ICON_CACHE.containsKey(this.data.getModId())) { return ITEM_ICON_CACHE.get(this.data.getModId()); } @@ -722,8 +732,8 @@ private ItemStack getItemIcon() { } // Gets the raw item icon resource string - String itemIcon = data.getItemIcon(); - if (!itemIcon.isBlank()) { + String itemIcon = this.data.getItemIcon(); + if (itemIcon != null && !itemIcon.isBlank()) { try { // 0:mod id 1:item name (2:metadata) String[] parts = itemIcon.split(":"); @@ -740,7 +750,7 @@ private ItemStack getItemIcon() { } // If the mod doesn't specify an item to use, Catalogue will attempt to get an item from the mod - Optional optional = ForgeRegistries.ITEMS.getValuesCollection().stream().filter(item -> item.getRegistryName().getNamespace().equals(this.data.getModId())).map(ItemStack::new).findFirst(); + Optional optional = ForgeRegistries.ITEMS.getValuesCollection().stream().filter(item -> Objects.requireNonNull(item.getRegistryName()).getNamespace().equals(this.data.getModId())).map(ItemStack::new).findFirst(); if (optional.isPresent()) { ItemStack item = optional.get(); if (!item.isEmpty()) { @@ -748,7 +758,7 @@ private ItemStack getItemIcon() { if (item.getItem().getCreativeTab() != null) { try { ItemStack tabItem = item.getItem().getCreativeTab().getIcon(); - if (tabItem != null && !tabItem.isEmpty() && tabItem.getItem().getRegistryName().getNamespace().equals(this.data.getModId())) { + if (tabItem != null && !tabItem.isEmpty() && Objects.requireNonNull(tabItem.getItem().getRegistryName()).getNamespace().equals(this.data.getModId())) { item = tabItem; } } catch (Exception e) { @@ -765,6 +775,20 @@ private ItemStack getItemIcon() { private String getFormattedModName(boolean favouriteIconVisible) { String name = this.data.getDisplayName(); + name = this.getFormattedText(name, favouriteIconVisible); + if (this.data.isLibrary()) { + return TextFormatting.DARK_GRAY + name; + } + return name; + } + + @NotNull + private String getFormattedModVersion(boolean favouriteIconVisible) { + String version = this.data.getVersion(); + return TextFormatting.GRAY + this.getFormattedText(version, favouriteIconVisible); + } + + private String getFormattedText(String text, boolean favouriteIconVisible) { int paddingEnd = 4; int trimWidth = this.list.getListWidth() - 24 - paddingEnd; IModData.Update update = this.data.getUpdate(); @@ -774,13 +798,10 @@ private String getFormattedModName(boolean favouriteIconVisible) { if (favouriteIconVisible) { trimWidth -= 18; } - if (CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > trimWidth) { - name = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(name, trimWidth - 8).trim() + "..."; - } - if (this.data.isLibrary()) { - return TextFormatting.DARK_GRAY + name; + if (CatalogueModListScreen.this.fontRenderer.getStringWidth(text) > trimWidth) { + text = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(text, trimWidth - 8).trim() + "..."; } - return name; + return text; } public IModData getData() { @@ -820,6 +841,10 @@ public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relat public void updatePosition(int slotIndex, int x, int y, float partialTicks) { } + public boolean isMouseOver() { + return this.hovered; + } + private class PinnedButton extends GuiButton { private static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/icons.png"); @@ -833,7 +858,7 @@ public PinnedButton(String modId) { @Override public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTick) { if (!this.visible) return; - this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + this.hovered = ModListEntry.this.isMouseOver() && ClientHelper.isMouseWithin(this.x, this.y, this.width, this.height, mouseX, mouseY); this.mouseDragged(mc, mouseX, mouseY); int textureU = FAVOURITES.has(this.modId) ? 10 : 0; mc.getTextureManager().bindTexture(TEXTURE); @@ -842,20 +867,14 @@ public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTick) @Override public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY) && !CatalogueModListScreen.this.modList.hideFavourites) { + if (super.mousePressed(mc, mouseX, mouseY) && !ModListEntry.this.list.shouldHideFavourites()) { FAVOURITES.toggle(this.modId); ModListEntry.this.list.filterAndUpdateList(); this.playPressSound(mc.getSoundHandler()); - CatalogueModListScreen.this.updateSelectedModList(); return true; } return false; } - - @Override - public boolean isMouseOver() { - return super.isMouseOver() && CatalogueModListScreen.this.modList.isMouseOver(); - } } } @@ -921,21 +940,21 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { // Draw license String license = this.selectedModData.getLicense(); - if (!license.isBlank()) { + if (license != null && !license.isBlank()) { this.drawStringWithLabel("catalogue.gui.licenses", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); labelOffset -= 15; } // Draw credits String credits = this.selectedModData.getCredits(); - if (!credits.isBlank()) { + if (credits != null && !credits.isBlank()) { this.drawStringWithLabel("catalogue.gui.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); labelOffset -= 15; } // Draw authors String authors = this.selectedModData.getAuthors(); - if (!authors.isBlank()) { + if (authors != null && !authors.isBlank()) { this.drawStringWithLabel("catalogue.gui.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); } } else { @@ -953,9 +972,10 @@ public StringList(int width, int height, int left, int top) { this.visible = false; } - public void setTextFromInfo(IModData data) { + public void setTextFromInfo(@NotNull IModData data) { this.entries.clear(); this.visible = true; + if (data.getDescription() == null) return; if (data.getDescription().trim().isBlank()) { this.visible = false; return; @@ -1065,21 +1085,14 @@ public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relat */ @SuppressWarnings("SameParameterValue") private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { - String formatted = I18n.format(format, text); // Attempting to keep Forge's lang since it's already support many languages - String colon = formatted.contains(":") ? ":" : ":"; - String label = formatted.substring(0, formatted.indexOf(colon) + 1); - String content = formatted.substring(formatted.indexOf(colon) + 1); + String formatted = labelColor + I18n.format(format, contentColor + text); if (this.fontRenderer.getStringWidth(formatted) > maxWidth) { - content = this.fontRenderer.trimStringToWidth(content, maxWidth - this.fontRenderer.getStringWidth(label) - 7) + "..."; - String credits = labelColor + label; - credits += contentColor + content; - drawString(this.fontRenderer, credits, x, y, 0xFFFFFF); + formatted = this.fontRenderer.trimStringToWidth(formatted, maxWidth - 7) + "..."; if (ClientHelper.isMouseWithin(x, y, maxWidth, 9, mouseX, mouseY)) { // Sets the active tool tip if string is too long so users can still read it this.setActiveTooltip(text); } - } else { - drawString(this.fontRenderer, labelColor + label + contentColor + content, x, y, 0xFFFFFF); } + drawString(this.fontRenderer, formatted, x, y, 0xFFFFFF); } private ImageInfo getBanner(String modId) { @@ -1100,7 +1113,7 @@ private ImageInfo getBanner(String modId) { return MISSING_BANNER_INFO; } - private void loadAndCacheLogo(IModData data) { + private void loadAndCacheLogo(@NotNull IModData data) { if (BANNER_CACHE.containsKey(data.getModId())) return; // Fills an empty logo as logo may not be present @@ -1112,7 +1125,7 @@ private void loadAndCacheLogo(IModData data) { }); } - private void loadAndCacheIcon(IModData data) { + private void loadAndCacheIcon(@NotNull IModData data) { if (IMAGE_ICON_CACHE.containsKey(data.getModId())) return; // Fills an empty icon as icon may not be present @@ -1245,8 +1258,8 @@ private void setSelectedModData(IModData data) { this.issueButton.visible = true; this.configButton.enabled = data.hasConfig(); - this.websiteButton.enabled = !data.getHomepage().isBlank(); - this.issueButton.enabled = !data.getIssueTracker().isBlank(); + this.websiteButton.enabled = data.getHomepage() != null && !data.getHomepage().isBlank(); + this.issueButton.enabled = data.getIssueTracker() != null && !data.getIssueTracker().isBlank(); int contentLeft = this.modList.right + 12 + 10; int contentWidth = this.width - contentLeft - 10; @@ -1259,33 +1272,21 @@ private void setSelectedModData(IModData data) { } /** - * Gets the count of the footer text elements. This is used to corrrectly set the height of + * Gets the count of the footer text elements. This is used to correctly set the height of * the description widget. * * @param data the mod data * @return the count of footer text elements */ - private int getFooterTextElementCount(IModData data) { + private int getFooterTextElementCount(@NotNull IModData data) { int count = 0; - if (!data.getLicense().isBlank()) count++; - if (!data.getCredits().isBlank()) count++; - if (!data.getAuthors().isBlank()) count++; + if (data.getLicense() != null && !data.getLicense().isBlank()) count++; + if (data.getCredits() != null && !data.getCredits().isBlank()) count++; + if (data.getAuthors() != null && !data.getAuthors().isBlank()) count++; return count; } - @Override - public void onGuiClosed() { - FAVOURITES.save(); - } - - private void updateSelectedModList() { - ModListEntry selectedEntry = this.modList.getEntryFromInfo(this.selectedModData); - if (selectedEntry != null) { - this.modList.setSelected(selectedEntry); - } - } - - private void updateSearchFieldSuggestion(String value) { + private void updateSearchFieldSuggestion(@NotNull String value) { if (value.isEmpty()) { this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search") + "..."); } else if (value.startsWith("@")) { @@ -1307,7 +1308,7 @@ private void updateSearchFieldSuggestion(String value) { } } else { Optional optional = CACHED_MODS.values().stream().filter(data -> { - return data.getDisplayName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); + return ModList.FILTER_PREDICATE.test(data) && data.getDisplayName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); }).min(Comparator.comparing(IModData::getDisplayName)); if (optional.isPresent()) { int length = value.length(); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java index 06c3d985f..c9fa61f8e 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java @@ -5,7 +5,6 @@ import net.minecraft.client.gui.GuiOptions; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.resources.IResourcePack; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; @@ -31,73 +30,85 @@ public String getDisplayName() { } @Override - public @NotNull String getVersion() { + public String getVersion() { return "1.12.2"; } @Override - public @NotNull String getInnerVersion() { + public String getInnerVersion() { return this.getVersion(); } + @Nullable @Override - public @NotNull String getDescription() { + public String getDescription() { // Description provided by minecraft.wiki (CC BY-NC-SA 3.0) return "Minecraft is a 3D sandbox adventure game developed by Mojang Studios where players can interact with a fully customizable three-dimensional world made of blocks and entities. Its diverse gameplay options allow players to choose the way they play, creating countless possibilities."; } + @Nullable @Override - public @NotNull String getItemIcon() { - return ""; + public String getItemIcon() { + return null; } + @Nullable @Override - public @NotNull String getImageIcon() { - return ""; + public String getImageIcon() { + return null; } + @Nullable @Override - public @NotNull String getLicense() { + public String getLicense() { return "All Rights Reserved"; } + @Nullable @Override - public @NotNull String getCredits() { - return ""; + public String getCredits() { + return null; } + @Nullable @Override - public @NotNull String getAuthors() { + public String getAuthors() { return "Mojang AB"; } + @Nullable @Override - public @NotNull String getHomepage() { + public String getHomepage() { return "https://www.minecraft.net"; } + @Nullable @Override - public @NotNull String getIssueTracker() { + public String getIssueTracker() { return "https://bugs.mojang.com/projects/MC/issues"; } + @Nullable @Override - public @NotNull String getBanner() { - return ""; + public String getBanner() { + return null; } + @Nullable @Override - public @NotNull String getBackground() { - return ""; + public String getBackground() { + return null; } + @Nullable @Override - public @Nullable Update getUpdate() { + public Update getUpdate() { return null; } + @Nullable @Override - public @Nullable IResourcePack getResourcePack() { + public IResourcePack getResourcePack() { return null; } @@ -123,11 +134,11 @@ public void openConfigScreen(Minecraft minecraft, GuiScreen parent) { @Override public void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y) { - } + @Nullable @Override - public @NotNull String getUpdateText(Update update) { - return ""; + public String getUpdateText(Update update) { + return null; } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index dc6fe2ce5..ad2c29aba 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -59,7 +59,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { // Scroll Bar if (this.scrollBarVisible) { - this.drawScrollBar(tessellator, maxScroll); + this.drawScrollBar(maxScroll); } // Customized decorations. Empty by default. @@ -71,7 +71,8 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { GlStateManager.disableBlend(); } - protected void drawScrollBar(Tessellator tessellator, int maxScroll) { + protected void drawScrollBar(int maxScroll) { + Tessellator tessellator = Tessellator.getInstance(); BufferBuilder buffer = tessellator.getBuffer(); int scrollBarLeft = this.getScrollBarX(); @@ -145,7 +146,7 @@ protected void drawSelectionBox(int mouseX, int mouseY, float partialTicks) { @Deprecated @Override protected void drawSelectionBox(int contentLeft, int contentTop, int mouseXIn, int mouseYIn, float partialTicks) { - this.drawSelectionBox(mouseX, mouseY, partialTicks); + this.drawSelectionBox(mouseXIn, mouseYIn, partialTicks); } @Override diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java index 4693e2ec1..cf43306ea 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java @@ -65,8 +65,8 @@ public void renderScrollingString(FontRenderer font, String text, int centerX, i } protected int getFGColor() { - if (packedFGColour != 0) { - return packedFGColour; + if (this.packedFGColour != 0) { + return this.packedFGColour; } else if (!this.enabled) { return 10526880; } else if (this.hovered) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java index d68b4c7dc..136bf797b 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java @@ -3,6 +3,7 @@ import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.GuiTextField; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.function.BiFunction; @@ -57,12 +58,12 @@ public void drawTextBox() { currentDrawX = this.fontRenderer.drawStringWithShadow(formatText(rawTextBeforeCursor, this.lineScrollOffset), (float) textStartX, (float) textStartY, textColor); } - isTextTruncated = this.cursorPosition < this.getText().length() || this.getText().length() >= this.getMaxStringLength(); + this.isTextTruncated = this.cursorPosition < this.getText().length() || this.getText().length() >= this.getMaxStringLength(); int cursorDrawX = currentDrawX; if (!isCursorVisible) { cursorDrawX = cursorPosRelative > 0 ? textStartX + this.width : textStartX; - } else if (isTextTruncated) { + } else if (this.isTextTruncated) { cursorDrawX = currentDrawX - 1; --currentDrawX; } @@ -73,7 +74,7 @@ public void drawTextBox() { currentDrawX = this.fontRenderer.drawStringWithShadow(formatText(rawTextAfterCursor, this.cursorPosition), (float) currentDrawX, (float) textStartY, textColor); } - if (!isTextTruncated && this.suggestion != null) { + if (!this.isTextTruncated && this.suggestion != null) { if (!this.getText().isEmpty()) { this.fontRenderer.drawStringWithShadow(this.suggestion, (float) currentDrawX - 1, (float) textStartY, 0x808080); } else { @@ -82,7 +83,7 @@ public void drawTextBox() { } if (shouldDrawCursor) { - if (isTextTruncated) { + if (this.isTextTruncated) { Gui.drawRect(cursorDrawX, textStartY - 1, cursorDrawX + 1, textStartY + 1 + this.fontRenderer.FONT_HEIGHT, 0xFFCFCFD0); } else { this.fontRenderer.drawStringWithShadow("_", (float) cursorDrawX, (float) textStartY, textColor); @@ -101,7 +102,7 @@ public void setFormatter(@Nullable BiFunction pFormatte } private String formatText(String text, int cursorPos) { - return formatter != null ? formatter.apply(text, cursorPos) : text; + return this.formatter != null ? this.formatter.apply(text, cursorPos) : text; } // Responder @@ -109,17 +110,12 @@ public void setResponder(@Nullable Consumer pResponder) { this.responder = pResponder; } - private void onTextChange(String textIn) { - if (this.responder != null) { - this.responder.accept(textIn); - } - } - + // Patch vanilla missing methods @Override - public void setText(String textIn) { + public void setText(@NotNull String textIn) { super.setText(textIn); if (this.validator.apply(textIn)) { - this.onTextChange(textIn); + this.setResponderEntryValue(this.getId(), textIn); } } @@ -127,18 +123,21 @@ public void setText(String textIn) { public void setMaxStringLength(int length) { super.setMaxStringLength(length); if (this.getText().length() > length) { - this.onTextChange(this.getText()); + this.setResponderEntryValue(this.getId(), this.getText()); } } + // Call consumer responder @Override - public void moveCursorBy(int num) { - super.moveCursorBy(num); - this.onTextChange(this.getText()); + public void setResponderEntryValue(int idIn, String textIn) { + if (this.responder != null) { + this.responder.accept(textIn); + } + super.setResponderEntryValue(idIn, textIn); } // Suggestion - public void setSuggestion(String suggestion) { + public void setSuggestion(@NotNull String suggestion) { this.suggestion = suggestion; } @@ -146,8 +145,7 @@ public String getSuggestion() { return this.suggestion; } - public boolean getIsTextTruncated() { + public boolean isTextTruncated() { return this.isTextTruncated; } - } diff --git a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java index 50687d135..d95d27986 100644 --- a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java @@ -5,16 +5,22 @@ import com.cleanroommc.catalogue.client.CleanroomModData; import com.cleanroommc.catalogue.client.IModData; import com.cleanroommc.catalogue.platform.services.IPlatformHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiVideoSettings; import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModContainer; +import org.jetbrains.annotations.Nullable; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** * Author: MrCrayfish @@ -23,12 +29,26 @@ public class CleanroomPlatformHelper implements IPlatformHelper { @Override public List getAllModData() { - return Loader.instance().getActiveModList().stream().map(CleanroomModData::new).collect(Collectors.toList()); + List dataList = new ArrayList<>(); + // Handle special containers + ArrayList specialContainerList = new ArrayList<>(); + FMLClientHandler.instance().addSpecialModEntries(specialContainerList); + specialContainerList.removeIf(modContainer -> { + if (modContainer.getModId().equals("optifine")) { + dataList.add(new OFData(modContainer)); + return true; + } + return false; + }); + List containerList = Loader.instance().getActiveModList(); + containerList.addAll(specialContainerList); + dataList.addAll(containerList.stream().map(CleanroomModData::new).toList()); + return dataList; } @Override public File getModDirectory() { - return Loader.instance().getModDir(); + return Loader.instance().getModsDir(); } @Override @@ -66,4 +86,60 @@ public boolean getEnableIconLimit() { public int getIconLimit() { return CatalogueConfig.iconMaxWidthHeight; } + + private static class OFData extends CleanroomModData { + private OFData(ModContainer info) { + super(info); + } + + @Override + public String getVersion() { + String version = this.getInnerVersion(); + return version.substring(version.indexOf("OptiFine_1.12.2_") + 16); + } + + @Nullable + @Override + public String getDescription() { + // Copied from https://www.optifine.net/home + return """ + OptiFine is a Minecraft optimization mod. + It allows Minecraft to run faster and look better with full support for HD textures and many configuration options. + """; + } + + @Nullable + @Override + public String getLicense() { + return "All Rights Reserved"; + } + + @Nullable + @Override + public String getAuthors() { + return "sp614x"; + } + + @Nullable + @Override + public String getHomepage() { + return "https://www.optifine.net/home"; + } + + @Nullable + @Override + public String getIssueTracker() { + return "https://github.com/sp614x/optifine/issues"; + } + + @Override + public boolean hasConfig() { + return true; + } + + @Override + public void openConfigScreen(Minecraft minecraft, GuiScreen parent) { + minecraft.displayGuiScreen(new GuiVideoSettings(parent, minecraft.gameSettings)); + } + } } diff --git a/src/main/java/com/cleanroommc/common/CatalogueContainer.java b/src/main/java/com/cleanroommc/common/CatalogueContainer.java index 75d236aa0..c643dade6 100644 --- a/src/main/java/com/cleanroommc/common/CatalogueContainer.java +++ b/src/main/java/com/cleanroommc/common/CatalogueContainer.java @@ -2,6 +2,7 @@ import com.cleanroommc.catalogue.CatalogueConfig; import com.cleanroommc.catalogue.CatalogueConstants; +import com.cleanroommc.catalogue.client.ClientHandler; import com.google.common.eventbus.EventBus; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.ConfigManager; @@ -27,6 +28,7 @@ public CatalogueContainer() { meta.backgroundFile = "/assets/catalogue/background.png"; ConfigManager.register(CatalogueConfig.class); MinecraftForge.EVENT_BUS.register(CatalogueConfig.class); + MinecraftForge.EVENT_BUS.register(ClientHandler.class); } @Override diff --git a/src/main/java/net/minecraftforge/fml/client/GuiErrorBase.java b/src/main/java/net/minecraftforge/fml/client/GuiErrorBase.java index 038749580..8bc6f944d 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiErrorBase.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiErrorBase.java @@ -59,8 +59,7 @@ protected void actionPerformed(GuiButton button) { try { - File modsDir = new File(minecraftDir, "mods"); - Desktop.getDesktop().open(modsDir); + Desktop.getDesktop().open(Loader.instance().getModsDir()); } catch (Exception e) { diff --git a/src/main/java/net/minecraftforge/fml/client/GuiModList.java b/src/main/java/net/minecraftforge/fml/client/GuiModList.java index e5577b471..50048341d 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiModList.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiModList.java @@ -1,11 +1,630 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2020. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + package net.minecraftforge.fml.client; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; -import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.gui.GuiUtilRenderComponents; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.resources.IResourcePack; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StringUtils; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.common.ForgeVersion.CheckResult; +import net.minecraftforge.common.ForgeVersion.Status; +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.ModContainer.Disableable; +import net.minecraftforge.fml.common.ModMetadata; +import net.minecraftforge.fml.common.versioning.ComparableVersion; +import static net.minecraft.util.text.TextFormatting.*; +import org.lwjgl.input.Mouse; + +import com.google.common.base.Strings; +import org.lwjgl.opengl.GL11; + +/** + * @author cpw + * @deprecated Use {@link com.cleanroommc.catalogue.client.screen.CatalogueModListScreen} + */ @Deprecated -public class GuiModList extends CatalogueModListScreen { - public GuiModList(GuiScreen mainMenu) { - super(mainMenu); +@SuppressWarnings("DeprecatedIsStillUsed") +public class GuiModList extends GuiScreen +{ + private enum SortType implements Comparator + { + NORMAL(24), + A_TO_Z(25){ @Override protected int compare(String name1, String name2){ return name1.compareTo(name2); }}, + Z_TO_A(26){ @Override protected int compare(String name1, String name2){ return name2.compareTo(name1); }}; + + private int buttonID; + + private SortType(int buttonID) + { + this.buttonID = buttonID; + } + + @Nullable + public static SortType getTypeForButton(GuiButton button) + { + for (SortType t : values()) + { + if (t.buttonID == button.id) + { + return t; + } + } + return null; + } + + protected int compare(String name1, String name2){ return 0; } + + @Override + public int compare(ModContainer o1, ModContainer o2) + { + String name1 = StringUtils.stripControlCodes(o1.getName()).toLowerCase(); + String name2 = StringUtils.stripControlCodes(o2.getName()).toLowerCase(); + return compare(name1, name2); + } + } + + private GuiScreen mainMenu; + private GuiSlotModList modList; + private GuiScrollingList modInfo; + private int selected = -1; + private ModContainer selectedMod; + private int listWidth; + private ArrayList mods; + private GuiButton configModButton; + private GuiButton disableModButton; + + private int buttonMargin = 1; + private int numButtons = SortType.values().length; + + private String lastFilterText = ""; + + private GuiTextField search; + private boolean sorted = false; + private SortType sortType = SortType.NORMAL; + + /** + * @param mainMenu + */ + public GuiModList(GuiScreen mainMenu) + { + this.mainMenu = mainMenu; + this.mods = new ArrayList(); + FMLClientHandler.instance().addSpecialModEntries(mods); + // Add child mods to their parent's list + for (ModContainer mod : Loader.instance().getModList()) + { + if (mod.getMetadata() != null && mod.getMetadata().parentMod == null && !Strings.isNullOrEmpty(mod.getMetadata().parent)) + { + String parentMod = mod.getMetadata().parent; + ModContainer parentContainer = Loader.instance().getIndexedModList().get(parentMod); + if (parentContainer != null) + { + mod.getMetadata().parentMod = parentContainer; + parentContainer.getMetadata().childMods.add(mod); + continue; + } + } + else if (mod.getMetadata() != null && mod.getMetadata().parentMod != null) + { + continue; + } + mods.add(mod); + } + } + + @Override + public void initGui() + { + int slotHeight = 25; + for (ModContainer mod : mods) + { + listWidth = Math.max(listWidth,getFontRenderer().getStringWidth(mod.getName()) + 10); + listWidth = Math.max(listWidth,getFontRenderer().getStringWidth(mod.getVersion()) + 5 + slotHeight); + } + listWidth = Math.min(listWidth, 150); + this.modList = new GuiSlotModList(this, mods, listWidth, slotHeight); + + this.buttonList.add(new GuiButton(6, ((modList.right + this.width) / 2) - 100, this.height - 38, I18n.format("gui.done"))); + configModButton = new GuiButton(20, 10, this.height - 49, this.listWidth, 20, "Config"); + disableModButton = new GuiButton(21, 10, this.height - 27, this.listWidth, 20, "Disable"); + this.buttonList.add(configModButton); + this.buttonList.add(disableModButton); + + search = new GuiTextField(0, getFontRenderer(), 12, modList.bottom + 17, modList.listWidth - 4, 14); + search.setFocused(true); + search.setCanLoseFocus(true); + + int width = (modList.listWidth / numButtons); + int x = 10, y = 10; + GuiButton normalSort = new GuiButton(SortType.NORMAL.buttonID, x, y, width - buttonMargin, 20, I18n.format("fml.menu.mods.normal")); + normalSort.enabled = false; + buttonList.add(normalSort); + x += width + buttonMargin; + buttonList.add(new GuiButton(SortType.A_TO_Z.buttonID, x, y, width - buttonMargin, 20, "A-Z")); + x += width + buttonMargin; + buttonList.add(new GuiButton(SortType.Z_TO_A.buttonID, x, y, width - buttonMargin, 20, "Z-A")); + + updateCache(); + } + + @Override + protected void mouseClicked(int x, int y, int button) throws IOException + { + super.mouseClicked(x, y, button); + search.mouseClicked(x, y, button); + if (button == 1 && x >= search.x && x < search.x + search.width && y >= search.y && y < search.y + search.height) { + search.setText(""); + } + } + + @Override + protected void keyTyped(char c, int keyCode) throws IOException + { + super.keyTyped(c, keyCode); + search.textboxKeyTyped(c, keyCode); + } + + @Override + public void updateScreen() + { + super.updateScreen(); + search.updateCursorCounter(); + + if (!search.getText().equals(lastFilterText)) + { + reloadMods(); + sorted = false; + } + + if (!sorted) + { + reloadMods(); + Collections.sort(mods, sortType); + selected = modList.selectedIndex = mods.indexOf(selectedMod); + sorted = true; + } + } + + private void reloadMods() + { + ArrayList mods = modList.getMods(); + mods.clear(); + for (ModContainer m : Loader.instance().getModList()) + { + // If it passes the filter, and is not a child mod + if (m.getName().toLowerCase().contains(search.getText().toLowerCase()) && (m.getMetadata() == null || m.getMetadata().parentMod == null)) + { + mods.add(m); + } + } + this.mods = mods; + lastFilterText = search.getText(); + } + + @Override + protected void actionPerformed(GuiButton button) throws IOException + { + if (button.enabled) + { + SortType type = SortType.getTypeForButton(button); + + if (type != null) + { + for (GuiButton b : buttonList) + { + if (SortType.getTypeForButton(b) != null) + { + b.enabled = true; + } + } + button.enabled = false; + sorted = false; + sortType = type; + this.mods = modList.getMods(); + } + else + { + switch (button.id) + { + case 6: + { + this.mc.displayGuiScreen(this.mainMenu); + return; + } + case 20: + { + try + { + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod); + GuiScreen newScreen = guiFactory.createConfigGui(this); + this.mc.displayGuiScreen(newScreen); + } + catch (Exception e) + { + FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", selectedMod.getModId(), e); + } + return; + } + } + } + } + super.actionPerformed(button); + } + + public int drawLine(String line, int offset, int shifty) + { + this.fontRenderer.drawString(line, offset, shifty, 0xd7edea); + return shifty + 10; + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) + { + this.modList.drawScreen(mouseX, mouseY, partialTicks); + if (this.modInfo != null) + this.modInfo.drawScreen(mouseX, mouseY, partialTicks); + + int left = ((this.width - this.listWidth - 38) / 2) + this.listWidth + 30; + this.drawCenteredString(this.fontRenderer, "Mod List", left, 16, 0xFFFFFF); + super.drawScreen(mouseX, mouseY, partialTicks); + + String text = I18n.format("fml.menu.mods.search"); + int x = ((10 + modList.right) / 2) - (getFontRenderer().getStringWidth(text) / 2); + getFontRenderer().drawString(text, x, modList.bottom + 5, 0xFFFFFF); + search.drawTextBox(); + } + + @Override + public void handleMouseInput() throws IOException + { + int mouseX = Mouse.getEventX() * this.width / this.mc.displayWidth; + int mouseY = this.height - Mouse.getEventY() * this.height / this.mc.displayHeight - 1; + + super.handleMouseInput(); + if (this.modInfo != null) + this.modInfo.handleMouseInput(mouseX, mouseY); + this.modList.handleMouseInput(mouseX, mouseY); + } + + Minecraft getMinecraftInstance() + { + return mc; + } + + FontRenderer getFontRenderer() + { + return fontRenderer; + } + + public void selectModIndex(int index) + { + if (index == this.selected) + return; + this.selected = index; + this.selectedMod = (index >= 0 && index <= mods.size()) ? mods.get(selected) : null; + + updateCache(); + } + + public boolean modIndexSelected(int index) + { + return index == selected; + } + + private void updateCache() + { + configModButton.visible = false; + disableModButton.visible = false; + modInfo = null; + + if (selectedMod == null) + return; + + ResourceLocation logoPath = null; + Dimension logoDims = new Dimension(0, 0); + List lines = new ArrayList(); + ModMetadata metadata = selectedMod.getMetadata(); + if (metadata != null) { + String logoFile = metadata.logoFile; + if (!logoFile.isEmpty()) { + TextureManager tm = mc.getTextureManager(); + IResourcePack pack = FMLClientHandler.instance().getResourcePackFor(selectedMod.getModId()); + try { + BufferedImage logo = null; + if (pack != null) { + logo = pack.getPackImage(); + } else { + InputStream logoResource = getClass().getResourceAsStream(logoFile); + if (logoResource != null) + logo = TextureUtil.readBufferedImage(logoResource); + } + if (logo != null) { + logoPath = tm.getDynamicTextureLocation("modlogo", new DynamicTexture(logo)); + logoDims = new Dimension(logo.getWidth(), logo.getHeight()); + } + } catch (IOException ignored) { + } + } + } + + if (metadata != null && !metadata.autogenerated) + { + disableModButton.visible = true; + disableModButton.enabled = true; + disableModButton.packedFGColour = 0; + Disableable disableable = selectedMod.canBeDisabled(); + if (disableable == Disableable.RESTART) + { + disableModButton.packedFGColour = 0xFF3377; + } + else if (disableable != Disableable.YES) + { + disableModButton.enabled = false; + } + + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod); + configModButton.visible = true; + configModButton.enabled = false; + if (guiFactory != null) + { + configModButton.enabled = guiFactory.hasConfigGui(); + } + lines.add(metadata.name); + if (selectedMod.getDisplayVersion().isEmpty()) + lines.add(String.format("Version: %s", selectedMod.getVersion())); + else if (selectedMod.getVersion().equals(selectedMod.getDisplayVersion())) + lines.add(String.format("Version: %s", selectedMod.getDisplayVersion())); + else + lines.add(String.format("Version: %s (%s)", selectedMod.getDisplayVersion(), selectedMod.getVersion())); + lines.add(String.format("Mod ID: '%s' Mod State: %s", selectedMod.getModId(), Loader.instance().getModState(selectedMod))); + + if (!metadata.credits.isEmpty()) + { + lines.add("Credits: " + metadata.credits); + } + + if (!metadata.getAuthorList().isEmpty()) + lines.add("Authors: " + metadata.getAuthorList()); + if (!metadata.url.isEmpty()) + lines.add("URL: " + metadata.url); + + if (!metadata.childMods.isEmpty()) + lines.add("Child mods: " + metadata.getChildModList()); + } + else + { + lines.add(WHITE + selectedMod.getName()); + lines.add(WHITE + "Version: " + selectedMod.getVersion()); + lines.add(WHITE + "Mod State: " + Loader.instance().getModState(selectedMod)); + } + + CheckResult vercheck = ForgeVersion.getCleanResult(selectedMod); + if (vercheck != null && (vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED || vercheck.status == Status.BETA || vercheck.status == Status.AHEAD)) + { + lines.add(null); + if (vercheck.status == Status.BETA) + lines.add(GOLD + "This version is a beta version"); + else if (vercheck.status == Status.AHEAD) + lines.add(LIGHT_PURPLE + "This version is ahead of the latest version found (" + LIGHT_PURPLE + vercheck.latestFound + ")"); + else if (vercheck.status == Status.BETA_OUTDATED) + lines.add(GOLD + "Beta update (" + GOLD + vercheck.latestFound + ") available: " + (vercheck.homepage == null ? "" : vercheck.homepage)); + else + lines.add(GREEN + "Update (" + GREEN + vercheck.latestFound + ") available: " + (vercheck.homepage == null ? "" : vercheck.homepage)); + + if (!vercheck.changes.isEmpty()) + { + lines.add(null); + lines.add("Changes:"); + for (Entry entry : vercheck.changes.entrySet()) + { + lines.add(" " + entry.getKey() + ":"); + lines.add(entry.getValue()); + lines.add(null); + } + } + } + + if (metadata != null && !metadata.autogenerated) + { + lines.add(null); + lines.add(metadata.description); + } + else + { + lines.add(null); + lines.add(RED + "No mod information found"); + lines.add(RED + "Ask the mod author to provide a mcmod.info file"); + } + + modInfo = new Info(this.width - this.listWidth - 30, lines, logoPath, logoDims); + } + + private class Info extends GuiScrollingList + { + @Nullable + private ResourceLocation logoPath; + private Dimension logoDims; + private List lines = null; + + public Info(int width, List lines, @Nullable ResourceLocation logoPath, Dimension logoDims) + { + super(GuiModList.this.getMinecraftInstance(), + width, + GuiModList.this.height, + 32, GuiModList.this.height - 88 + 4, + GuiModList.this.listWidth + 20, 60, + GuiModList.this.width, + GuiModList.this.height); + this.lines = resizeContent(lines); + this.logoPath = logoPath; + this.logoDims = logoDims; + + this.setHeaderInfo(true, getHeaderHeight()); + } + + @Override protected int getSize() { return 0; } + @Override protected void elementClicked(int index, boolean doubleClick) { } + @Override protected boolean isSelected(int index) { return false; } + @Override protected void drawBackground() {} + @Override protected void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess) { } + + private List resizeContent(List lines) + { + List ret = new ArrayList(); + for (String line : lines) + { + if (line == null) + { + ret.add(null); + continue; + } + + ITextComponent chat = ForgeHooks.newChatWithLinks(line, false); + int maxTextLength = this.listWidth - 8; + if (maxTextLength >= 0) + { + ret.addAll(GuiUtilRenderComponents.splitText(chat, maxTextLength, GuiModList.this.fontRenderer, false, true)); + } + } + return ret; + } + + private int getHeaderHeight() + { + int height = 0; + if (logoPath != null) + { + double scaleX = logoDims.width / 200.0; + double scaleY = logoDims.height / 65.0; + double scale = 1.0; + if (scaleX > 1 || scaleY > 1) + { + scale = 1.0 / Math.max(scaleX, scaleY); + } + logoDims.width *= scale; + logoDims.height *= scale; + + height += logoDims.height; + height += 10; + } + height += (lines.size() * 10); + if (height < this.bottom - this.top - 8) height = this.bottom - this.top - 8; + return height; + } + + + @Override + protected void drawHeader(int entryRight, int relativeY, Tessellator tess) + { + int top = relativeY; + + if (logoPath != null) + { + GlStateManager.enableBlend(); + GuiModList.this.mc.renderEngine.bindTexture(logoPath); + BufferBuilder wr = tess.getBuffer(); + int offset = (this.left + this.listWidth/2) - (logoDims.width / 2); + wr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + wr.pos(offset, top + logoDims.height, zLevel).tex(0, 1).endVertex(); + wr.pos(offset + logoDims.width, top + logoDims.height, zLevel).tex(1, 1).endVertex(); + wr.pos(offset + logoDims.width, top, zLevel).tex(1, 0).endVertex(); + wr.pos(offset, top, zLevel).tex(0, 0).endVertex(); + tess.draw(); + GlStateManager.disableBlend(); + top += logoDims.height + 10; + } + + for (ITextComponent line : lines) + { + if (line != null) + { + GlStateManager.enableBlend(); + GuiModList.this.fontRenderer.drawStringWithShadow(line.getFormattedText(), this.left + 4, top, 0xFFFFFF); + GlStateManager.disableAlpha(); + GlStateManager.disableBlend(); + } + top += 10; + } + } + + @Override + protected void clickHeader(int x, int y) + { + int offset = y; + if (logoPath != null) { + offset -= logoDims.height + 10; + } + if (offset <= 0) + return; + + int lineIdx = offset / 10; + if (lineIdx >= lines.size()) + return; + + ITextComponent line = lines.get(lineIdx); + if (line != null) + { + int k = -4; + for (ITextComponent part : line) { + if (!(part instanceof TextComponentString)) + continue; + k += GuiModList.this.fontRenderer.getStringWidth(((TextComponentString)part).getText()); + if (k >= x) + { + GuiModList.this.handleComponentClick(part); + break; + } + } + } + } } } diff --git a/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java b/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java new file mode 100644 index 000000000..af68601de --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java @@ -0,0 +1,132 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2020. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.fml.client; + +import java.util.ArrayList; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StringUtils; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.common.ForgeVersion.CheckResult; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.LoaderState.ModState; +import net.minecraftforge.fml.common.ModContainer; + +import static net.minecraft.util.text.TextFormatting.*; + +/** + * @author cpw + * + */ +public class GuiSlotModList extends GuiScrollingList +{ + + private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); + + private GuiModList parent; + private ArrayList mods; + + public GuiSlotModList(GuiModList parent, ArrayList mods, int listWidth, int slotHeight) + { + super(parent.getMinecraftInstance(), listWidth, parent.height, 32, parent.height - 88 + 4, 10, slotHeight, parent.width, parent.height); + this.parent = parent; + this.mods = mods; + } + + @Override + protected int getSize() + { + return mods.size(); + } + + @Override + protected void elementClicked(int index, boolean doubleClick) + { + this.parent.selectModIndex(index); + } + + @Override + protected boolean isSelected(int index) + { + return this.parent.modIndexSelected(index); + } + + @Override + protected void drawBackground() + { + this.parent.drawDefaultBackground(); + } + + @Override + protected int getContentHeight() + { + return (this.getSize()) * 25 + 1; + } + + ArrayList getMods() + { + return mods; + } + + @Override + protected void drawSlot(int idx, int right, int top, int height, Tessellator tess) + { + ModContainer mc = mods.get(idx); + String name = StringUtils.stripControlCodes(mc.getName()); + String version = StringUtils.stripControlCodes(mc.getDisplayVersion().isEmpty() ? mc.getVersion() : mc.getDisplayVersion()); + FontRenderer font = this.parent.getFontRenderer(); + CheckResult vercheck = ForgeVersion.getCleanResult(mc); + + if (Loader.instance().getModState(mc) == ModState.DISABLED) + { + drawString(name, 10, top + 2, RED); + drawString(version, 5 + height, top + 12, RED); + } + else + { + drawString(name, 10, top + 2, WHITE); + drawString(version, 5 + height, top + 12, WHITE); + + if (vercheck != null && vercheck.status.shouldDraw()) + { + Minecraft.getMinecraft().getTextureManager().bindTexture(VERSION_CHECK_ICONS); + GlStateManager.color(1, 1, 1, 1); + GlStateManager.pushMatrix(); + Gui.drawModalRectWithCustomSizedTexture(right - (height / 2 + 4), top + (height / 2 - 4), vercheck.status.getSheetOffset() * 8, (vercheck.status.isAnimated() && ((System.currentTimeMillis() / 800 & 1)) == 1) ? 8 : 0, 8, 8, 64, 16); + GlStateManager.popMatrix(); + } + } + } + + protected void drawString(String text, int width, int y, TextFormatting color) + { + FontRenderer font = parent.getFontRenderer(); + ITextComponent textComponent = new TextComponentString(color + font.trimStringToWidth(text, listWidth - width)); + font.drawString(textComponent.getFormattedText(), left + 3, y, 0); + } +} diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index ae56745e7..4f230e98e 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -708,7 +708,7 @@ public File getConfigDir() return canonicalConfigDir; } - public File getModDir() + public File getModsDir() { return canonicalModsDir; } diff --git a/src/main/java/net/minecraftforge/fml/common/MetadataCollection.java b/src/main/java/net/minecraftforge/fml/common/MetadataCollection.java index 226252695..c2a03cb38 100644 --- a/src/main/java/net/minecraftforge/fml/common/MetadataCollection.java +++ b/src/main/java/net/minecraftforge/fml/common/MetadataCollection.java @@ -43,9 +43,8 @@ public class MetadataCollection { - private String modListVersion; private ModMetadata[] modList; - private Map metadatas = Maps.newHashMap(); + private final Map metadatas = Maps.newHashMap(); public static MetadataCollection from(@Nullable InputStream inputStream, String sourceName) { @@ -59,8 +58,7 @@ public static MetadataCollection from(@Nullable InputStream inputStream, String { MetadataCollection collection; Gson gson = new GsonBuilder().registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionAdapter()).create(); - JsonParser parser = new JsonParser(); - JsonElement rootElement = parser.parse(reader); + JsonElement rootElement = JsonParser.parseReader(reader); if (rootElement.isJsonArray()) { collection = new MetadataCollection(); diff --git a/src/main/resources/assets/forge/lang/en_us.lang b/src/main/resources/assets/forge/lang/en_us.lang index c86a8a603..aeba234e4 100644 --- a/src/main/resources/assets/forge/lang/en_us.lang +++ b/src/main/resources/assets/forge/lang/en_us.lang @@ -235,6 +235,8 @@ forge.controlsgui.control.mac=CMD + %s forge.controlsgui.alt=ALT + %s fml.menu.mods=Mods +fml.menu.mods.normal=Normal +fml.menu.mods.search=Search: item.forge.bucketFilled.name=%s Bucket diff --git a/src/main/resources/assets/forge/lang/zh_cn.lang b/src/main/resources/assets/forge/lang/zh_cn.lang index f2c566197..cd8fa7a65 100644 --- a/src/main/resources/assets/forge/lang/zh_cn.lang +++ b/src/main/resources/assets/forge/lang/zh_cn.lang @@ -203,6 +203,8 @@ forge.controlsgui.control.mac=CMD + %s forge.controlsgui.alt=ALT + %s fml.menu.mods=模组 +fml.menu.mods.normal=普通 +fml.menu.mods.search=搜索: item.forge.bucketFilled.name=%s桶 From 909b13cdd3f566c92e06c6606fe2a6917f53573b Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:18:21 +0800 Subject: [PATCH 55/91] Fix suggestion overload --- .../client/screen/widget/CatalogueTextField.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java index 136bf797b..39cb8b5b5 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java @@ -11,8 +11,9 @@ public class CatalogueTextField extends GuiTextField { private final FontRenderer fontRenderer; - private String suggestion = ""; private boolean isTextTruncated; + @NotNull + private String suggestion = ""; @Nullable private Consumer responder; @Nullable @@ -74,12 +75,10 @@ public void drawTextBox() { currentDrawX = this.fontRenderer.drawStringWithShadow(formatText(rawTextAfterCursor, this.cursorPosition), (float) currentDrawX, (float) textStartY, textColor); } - if (!this.isTextTruncated && this.suggestion != null) { - if (!this.getText().isEmpty()) { - this.fontRenderer.drawStringWithShadow(this.suggestion, (float) currentDrawX - 1, (float) textStartY, 0x808080); - } else { - this.fontRenderer.drawStringWithShadow(this.suggestion, (float) currentDrawX, (float) textStartY, 0x808080); - } + if (!this.isTextTruncated && !this.suggestion.isEmpty()) { + int suggestionDrawX = this.getText().isEmpty() ? currentDrawX : currentDrawX - 1; + String suggestion = this.fontRenderer.trimStringToWidth(this.suggestion, textStartX + this.getWidth() - suggestionDrawX); + this.fontRenderer.drawStringWithShadow(suggestion, (float) suggestionDrawX, (float) textStartY, 0x808080); } if (shouldDrawCursor) { From 89a5c54cbbeffc0b62d17c24727fa771763e7f53 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 21 Nov 2025 22:46:10 +0800 Subject: [PATCH 56/91] Add config to disable Catalogue mod --- .../java/com/cleanroommc/catalogue/CatalogueConfig.java | 8 ++++++++ .../com/cleanroommc/catalogue/client/ClientHandler.java | 7 ++----- src/main/resources/assets/catalogue/lang/en_au.lang | 2 ++ src/main/resources/assets/catalogue/lang/en_us.lang | 2 ++ src/main/resources/assets/catalogue/lang/zh_cn.lang | 2 ++ 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index c235da4b3..364291700 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -8,6 +8,14 @@ @Config(modid = CatalogueConstants.MOD_ID) public class CatalogueConfig { + + @Config.Comment({ + "Whether enable Catalogue mod.", + "Setting it false will stop Catalogue redirecting Forge's mod list calls." + }) + @Config.LangKey("catalogue.config.enable_mod") + public static boolean enableMod = true; + @Config.RequiresMcRestart @Config.Comment({ "The list of library mods' mod ids.", diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java index d08439edf..6c9ff227a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java @@ -1,13 +1,10 @@ package com.cleanroommc.catalogue.client; -import com.cleanroommc.catalogue.CatalogueConstants; +import com.cleanroommc.catalogue.CatalogueConfig; import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; import net.minecraft.client.Minecraft; import net.minecraftforge.client.event.GuiOpenEvent; -import net.minecraftforge.common.config.Config; -import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.fml.client.GuiModList; -import net.minecraftforge.fml.client.event.ConfigChangedEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import org.jetbrains.annotations.NotNull; @@ -18,7 +15,7 @@ public class ClientHandler { @SubscribeEvent public static void onOpenScreen(@NotNull GuiOpenEvent event) { //noinspection deprecation - if (event.getGui() instanceof GuiModList) { + if (CatalogueConfig.enableMod && event.getGui() instanceof GuiModList) { event.setGui(new CatalogueModListScreen(Minecraft.getMinecraft().currentScreen)); } } diff --git a/src/main/resources/assets/catalogue/lang/en_au.lang b/src/main/resources/assets/catalogue/lang/en_au.lang index 80cac5269..47e375a78 100644 --- a/src/main/resources/assets/catalogue/lang/en_au.lang +++ b/src/main/resources/assets/catalogue/lang/en_au.lang @@ -38,6 +38,8 @@ catalogue.gui.website=Website catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s +catalogue.config.enable_mod=Enable Mod +catalogue.config.enable_mod.tooltip=Whether enable Catalogue mod. \nSetting it false will stop Catalogue redirecting Forge's mod list calls. catalogue.config.library_list=Library List catalogue.config.library_list.tooltip=The list of library mods' mod ids.\nThey will have grey names in the mod list. catalogue.config.ignored_dependencies_list=Ignored Dependencies List diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index 8e1d53c11..001cf8dc0 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -38,6 +38,8 @@ catalogue.gui.website=Website catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s +catalogue.config.enable_mod=Enable Mod +catalogue.config.enable_mod.tooltip=Whether enable Catalogue mod. \nSetting it false will stop Catalogue redirecting Forge's mod list calls. catalogue.config.library_list=Library List catalogue.config.library_list.tooltip=The list of library mods' mod ids.\nThey will have grey names in the mod list. catalogue.config.ignored_dependencies_list=Ignored Dependencies List diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index 67a2ea03e..5ccc08980 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -38,6 +38,8 @@ catalogue.gui.website=网站 catalogue.gui.submit_bug=提交Bug catalogue.gui.mod_id=Mod ID:%s +catalogue.config.enable_mod=启用模组 +catalogue.config.enable_mod.tooltip=控制是否启用Catalogue模组。 \n设置为否会阻止Catalogue重定向Forge模组列表的调用。 catalogue.config.library_list=库列表 catalogue.config.library_list.tooltip=库模组的Mod ID列表。\n它们会在模组列表中显示灰色名字。 catalogue.config.ignored_dependencies_list=忽略前置列表 From 66c1d3b2e4356fa525a20d08aac7ac0883f0a668 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 22 Nov 2025 22:55:46 +0800 Subject: [PATCH 57/91] Fix update click --- .../com/cleanroommc/catalogue/client/CleanroomModData.java | 4 ++-- .../catalogue/client/screen/CatalogueModListScreen.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index 196785f4d..f08fb9992 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -195,13 +195,13 @@ public String getUpdateText(Update update) { case AHEAD: return TextFormatting.LIGHT_PURPLE + I18n.format("catalogue.gui.ahead", update.latestFound()); case BETA_OUTDATED: - if (update.homepage() != null) { + if (update.homepage() != null && !update.homepage().isBlank()) { return TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available", update.latestFound(), update.homepage()); } else { return TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available_no_page", update.latestFound()); } case OUTDATED: - if (update.homepage() != null) { + if (update.homepage() != null && !update.homepage().isBlank()) { return TextFormatting.GREEN + I18n.format("catalogue.gui.update_available", update.latestFound(), update.homepage()); } else { return TextFormatting.GREEN + I18n.format("catalogue.gui.update_available_no_page", update.latestFound()); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 40dc0171d..28378e263 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -353,12 +353,13 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti // Version check button if (this.selectedModData != null) { int contentLeft = this.modList.right + 12 + 10; - String version = this.selectedModData.getVersion(); + String version = I18n.format("catalogue.gui.version", this.selectedModData.getVersion()); int versionWidth = this.fontRenderer.getStringWidth(version); if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { IModData.Update update = this.selectedModData.getUpdate(); if (update != null && update.homepage() != null && !update.homepage().isBlank() && update.updatable()) { this.openLink(update.homepage()); + return; } } } From 431e418367c65460cf3d90924f19d954431c4172 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 23 Nov 2025 10:58:41 +0800 Subject: [PATCH 58/91] Update CatalogueTextField.java --- .../catalogue/client/screen/widget/CatalogueTextField.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java index 39cb8b5b5..6a9529d64 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java @@ -114,7 +114,7 @@ public void setResponder(@Nullable Consumer pResponder) { public void setText(@NotNull String textIn) { super.setText(textIn); if (this.validator.apply(textIn)) { - this.setResponderEntryValue(this.getId(), textIn); + this.setResponderEntryValue(this.getId(), this.getText()); } } From 38a57fe412ec9c133c3bf9b851660cf47ef518df Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 23 Nov 2025 14:01:21 +0800 Subject: [PATCH 59/91] Fix list mouse --- .../screen/widget/CatalogueListExtended.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index ad2c29aba..7b93b5c4e 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -7,6 +7,7 @@ import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.math.MathHelper; +import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; public abstract class CatalogueListExtended extends GuiListExtended { @@ -143,6 +144,88 @@ protected void drawSelectionBox(int mouseX, int mouseY, float partialTicks) { } } + @Override + public void handleMouseInput() { + if (this.isMouseYWithinSlotBounds(this.mouseY)) { + + if (Mouse.getEventButton() == 0 && Mouse.getEventButtonState() && this.mouseY >= this.top && this.mouseY <= this.bottom) { + int listLeft = this.getListLeft(); + int listRight = this.getListRight(); + + int relativeY = this.mouseY - this.top - this.headerPadding + (int) this.amountScrolled - 4; + int slotIndex = relativeY / this.slotHeight; + + if (slotIndex < this.getSize() && this.mouseX >= listLeft && this.mouseX <= listRight && slotIndex >= 0 && relativeY >= 0) { + this.elementClicked(slotIndex, false, this.mouseX, this.mouseY); + this.selectedElement = slotIndex; + } else if (this.mouseX >= listLeft && this.mouseX <= listRight && relativeY < 0) { + this.clickedHeader(this.mouseX - listLeft, this.mouseY - this.top + (int) this.amountScrolled - 4); + } + } + + if (Mouse.isButtonDown(0) && this.getEnabled()) { + if (this.initialClickY == -1) { + boolean clickedOnList = true; + + if (this.mouseY >= this.top && this.mouseY <= this.bottom) { + int listLeft = this.getListLeft(); + int listRight = this.getListRight(); + int relativeY = this.mouseY - this.top - this.headerPadding + (int) this.amountScrolled - 4; + int slotIndex = relativeY / this.slotHeight; + + if (slotIndex < this.getSize() && this.mouseX >= listLeft && this.mouseX <= listRight && slotIndex >= 0 && relativeY >= 0) { + boolean isDoubleClick = slotIndex == this.selectedElement && Minecraft.getSystemTime() - this.lastClicked < 250L; + this.elementClicked(slotIndex, isDoubleClick, this.mouseX, this.mouseY); + this.selectedElement = slotIndex; + this.lastClicked = Minecraft.getSystemTime(); + } else if (this.mouseX >= listLeft && this.mouseX <= listRight && relativeY < 0) { + this.clickedHeader(this.mouseX - listLeft, this.mouseY - this.top + (int) this.amountScrolled - 4); + clickedOnList = false; + } + + int scrollBarLeft = this.getScrollBarX(); + int scrollBarRight = scrollBarLeft + 6; + + if (this.mouseX >= scrollBarLeft && this.mouseX <= scrollBarRight) { + this.scrollMultiplier = -1.0F; + + int maxScroll = Math.max(1, this.getMaxScroll()); + + int viewHeight = this.bottom - this.top; + int scrollBarHeight = (int) ((float) (viewHeight * viewHeight) / (float) this.getContentHeight()); + + scrollBarHeight = MathHelper.clamp(scrollBarHeight, 32, viewHeight - 8); + + this.scrollMultiplier /= (float) (viewHeight - scrollBarHeight) / (float) maxScroll; + } else { + this.scrollMultiplier = 1.0F; + } + + if (clickedOnList) { + this.initialClickY = this.mouseY; + } else { + this.initialClickY = -2; + } + } else { + this.initialClickY = -2; + } + } else if (this.initialClickY >= 0) { + this.amountScrolled -= (float) (this.mouseY - this.initialClickY) * this.scrollMultiplier; + this.initialClickY = this.mouseY; + } + } else { + this.initialClickY = -1; + } + + int dWheel = Mouse.getEventDWheel(); + + if (dWheel != 0) { + dWheel = dWheel > 0 ? -1 : 1; + this.amountScrolled += (float) (dWheel * this.slotHeight / 2); + } + } + } + @Deprecated @Override protected void drawSelectionBox(int contentLeft, int contentTop, int mouseXIn, int mouseYIn, float partialTicks) { From a6175c5da1e6d6a7e24a2c1a634bed641449a0a2 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 23 Nov 2025 15:48:17 +0800 Subject: [PATCH 60/91] Fix responder call --- .../catalogue/client/screen/widget/CatalogueTextField.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java index 6a9529d64..b57989cef 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -112,16 +113,18 @@ public void setResponder(@Nullable Consumer pResponder) { // Patch vanilla missing methods @Override public void setText(@NotNull String textIn) { + String previousText = this.getText(); super.setText(textIn); - if (this.validator.apply(textIn)) { + if (!Objects.equals(this.getText(), previousText)) { this.setResponderEntryValue(this.getId(), this.getText()); } } @Override public void setMaxStringLength(int length) { + String previousText = this.getText(); super.setMaxStringLength(length); - if (this.getText().length() > length) { + if (!Objects.equals(this.getText(), previousText)) { this.setResponderEntryValue(this.getId(), this.getText()); } } From 9107c700560ad3f2be5116d1051336ce86ed10d8 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:16:33 +0800 Subject: [PATCH 61/91] Improve scissor --- .../client/screen/CatalogueModListScreen.java | 18 ------------------ .../screen/widget/CatalogueListExtended.java | 9 ++++----- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 28378e263..1f5c81374 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -490,10 +490,7 @@ public ModList() { @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { - ClientHelper.scissor(this.getListLeft(), this.top, this.width, this.bottom - this.top); super.drawScreen(mouseX, mouseY, partialTicks); - GL11.glDisable(GL11.GL_SCISSOR_TEST); - if (this.children.isEmpty()) { String text = I18n.format("catalogue.gui.no_mods"); int left = this.left + this.width / 2; @@ -578,10 +575,6 @@ protected void drawContainerBackground(@NotNull Tessellator tessellator) { super.drawContainerBackground(tessellator); } - @Override - protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { - } - @Override public void handleMouseInput() { this.hideFavourites = Mouse.getEventDWheel() != 0; @@ -987,13 +980,6 @@ public void setTextFromInfo(@NotNull IModData data) { } } - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - ClientHelper.scissor(this.left, this.top, this.width, this.bottom - this.top); - super.drawScreen(mouseX, mouseY, partialTicks); - GL11.glDisable(GL11.GL_SCISSOR_TEST); - } - @Override protected void drawContainerBackground(@Nullable Tessellator tessellator) { int x = this.left; @@ -1039,10 +1025,6 @@ protected int getSize() { public IGuiListEntry getListEntry(int index) { return this.entries.get(index); } - - @Override - protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { - } } private class StringEntry implements CatalogueListExtended.IGuiListEntry { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index 7b93b5c4e..789fd38be 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -1,5 +1,6 @@ package com.cleanroommc.catalogue.client.screen.widget; +import com.cleanroommc.catalogue.client.ClientHelper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiListExtended; import net.minecraft.client.renderer.BufferBuilder; @@ -32,6 +33,8 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { int maxScroll = this.getMaxScroll(); this.scrollBarVisible = maxScroll > 0 && this.getContentHeight() != 0; + ClientHelper.scissor(this.left, this.top, this.width, this.bottom - this.top); + GlStateManager.disableLighting(); GlStateManager.disableFog(); Tessellator tessellator = Tessellator.getInstance(); @@ -47,11 +50,6 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawSelectionBox(mouseX, mouseY, partialTicks); GlStateManager.disableDepth(); - - // Draw overlay dirt to hide scrolled entries - this.overlayBackground(0, this.top, 255, 255); - this.overlayBackground(this.bottom, this.height, 255, 255); - GlStateManager.enableBlend(); GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ZERO, GlStateManager.DestFactor.ONE); GlStateManager.disableAlpha(); @@ -70,6 +68,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { GlStateManager.shadeModel(GL11.GL_FLAT); GlStateManager.enableAlpha(); GlStateManager.disableBlend(); + GL11.glDisable(GL11.GL_SCISSOR_TEST); } protected void drawScrollBar(int maxScroll) { From 51efc2b86536aa07a7b51d6d4b980511a0800767 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 28 Nov 2025 22:02:33 +0800 Subject: [PATCH 62/91] Fix parent screen, use reflection --- .../com/cleanroommc/catalogue/client/ClientHandler.java | 6 +++--- .../catalogue/client/screen/CatalogueModListScreen.java | 5 ++++- src/main/java/net/minecraftforge/fml/client/GuiModList.java | 4 ++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java index 6c9ff227a..40faef8d0 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java @@ -2,7 +2,6 @@ import com.cleanroommc.catalogue.CatalogueConfig; import com.cleanroommc.catalogue.client.screen.CatalogueModListScreen; -import net.minecraft.client.Minecraft; import net.minecraftforge.client.event.GuiOpenEvent; import net.minecraftforge.fml.client.GuiModList; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -14,9 +13,10 @@ public class ClientHandler { @SubscribeEvent public static void onOpenScreen(@NotNull GuiOpenEvent event) { + if (!CatalogueConfig.enableMod) return; //noinspection deprecation - if (CatalogueConfig.enableMod && event.getGui() instanceof GuiModList) { - event.setGui(new CatalogueModListScreen(Minecraft.getMinecraft().currentScreen)); + if (event.getGui() instanceof GuiModList screen) { + event.setGui(new CatalogueModListScreen(screen.getParentScreen())); } } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 1f5c81374..da73a0be5 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -39,6 +39,7 @@ import org.lwjgl.opengl.GL11; import java.awt.*; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -207,7 +208,9 @@ public void actionPerformed(@NotNull GuiButton button) { break; case 2: try { - Desktop.getDesktop().open(ClientServices.PLATFORM.getModDirectory()); + Class oclass = Class.forName("java.awt.Desktop"); + Object object = oclass.getMethod("getDesktop", new Class[0]).invoke(null); + oclass.getMethod("open", File.class).invoke(object, ClientServices.PLATFORM.getModDirectory()); } catch (Exception e) { CatalogueConstants.LOG.error("Problem opening mods folder", e); } diff --git a/src/main/java/net/minecraftforge/fml/client/GuiModList.java b/src/main/java/net/minecraftforge/fml/client/GuiModList.java index 50048341d..181c0b806 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiModList.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiModList.java @@ -627,4 +627,8 @@ protected void clickHeader(int x, int y) } } } + + public GuiScreen getParentScreen() { + return this.mainMenu; + } } From cce5c9106982ade3a89ea100c99f29d02847172c Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:33:45 +0800 Subject: [PATCH 63/91] Simplify list creation --- .../catalogue/client/Branding.java | 2 +- .../client/screen/CatalogueModListScreen.java | 108 ++---------- .../screen/widget/CatalogueIconButton.java | 3 +- .../screen/widget/CatalogueListExtended.java | 154 +++++++++++++----- .../screen/widget/CatalogueListSelection.java | 66 ++++++++ .../screen/widget/CatalogueTextButton.java | 3 +- .../screen/widget/CatalogueTextField.java | 4 +- 7 files changed, 207 insertions(+), 133 deletions(-) create mode 100644 src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListSelection.java diff --git a/src/main/java/com/cleanroommc/catalogue/client/Branding.java b/src/main/java/com/cleanroommc/catalogue/client/Branding.java index 590a5b16a..b2c557d36 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/Branding.java +++ b/src/main/java/com/cleanroommc/catalogue/client/Branding.java @@ -31,7 +31,7 @@ public Optional loadResource(IModData data) { if (resource == null || resource.isBlank()) return Optional.empty(); String modId = data.getModId(); - BufferedImage image = null; + BufferedImage image; try { IResourcePack resourcePack = data.getResourcePack(); if (this.equals(Branding.BANNER) && resourcePack != null && !resource.startsWith("/")) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index da73a0be5..2a9bb48e2 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -10,7 +10,6 @@ import com.cleanroommc.catalogue.platform.ClientServices; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; @@ -38,7 +37,6 @@ import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; -import java.awt.*; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -46,13 +44,13 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.*; -import java.util.List; import java.util.function.BiPredicate; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; +@SuppressWarnings("CodeBlock2Expr") public class CatalogueModListScreen extends GuiScreen implements DropdownMenuHandler { private static final Favourites FAVOURITES = new Favourites(); private static final Comparator SORT_ALPHABETICALLY = Comparator.comparing(o -> o.getData().getDisplayName()); @@ -452,7 +450,7 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { } } - private class ModList extends CatalogueListExtended { + private class ModList extends CatalogueListSelection { private static final Predicate SEARCH_PREDICATE = data -> { String query = OPTION_QUERY.getValue(); if (query.startsWith("@")) { @@ -483,8 +481,6 @@ private class ModList extends CatalogueListExtended { } return true; }; - private final List children = new ArrayList<>(); - private @Nullable ModListEntry selected; private boolean hideFavourites; public ModList() { @@ -494,7 +490,7 @@ public ModList() { @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { super.drawScreen(mouseX, mouseY, partialTicks); - if (this.children.isEmpty()) { + if (this.children().isEmpty()) { String text = I18n.format("catalogue.gui.no_mods"); int left = this.left + this.width / 2; int top = this.top + (this.bottom - this.top - CatalogueModListScreen.this.fontRenderer.FONT_HEIGHT) / 2; @@ -511,31 +507,12 @@ public void filterAndUpdateList() { .collect(Collectors.toList()); this.replaceEntries(entries); if (CatalogueModListScreen.this.selectedModData != null) { - Optional selectedEntry = this.children.stream().filter(entry -> entry.data == CatalogueModListScreen.this.selectedModData).findFirst(); + Optional selectedEntry = this.children().stream().filter(entry -> entry.data == CatalogueModListScreen.this.selectedModData).findFirst(); selectedEntry.ifPresent(this::setSelected); } this.clampAmountScrolled(); } - public void centerScrollOn(ModListEntry pEntry) { - this.setAmountScrolled((float) (this.children.indexOf(pEntry) * this.slotHeight + this.slotHeight / 2 - (this.bottom - this.top) / 2)); - } - - protected void clearEntries() { - this.children.clear(); - this.setSelected(null); - } - - protected void replaceEntries(Collection entries) { - this.clearEntries(); - this.children.addAll(entries); - } - - @Override - public IGuiListEntry getListEntry(int index) { - return this.children.get(index); - } - @Override protected int getScrollBarX() { return this.left + this.width - 6; @@ -551,24 +528,6 @@ public int getListWidth() { return this.width - (this.isScrollBarVisible() ? 6 : 0); } - @Override - protected int getSize() { - return this.children.size(); - } - - public @Nullable ModListEntry getSelected() { - return this.selected; - } - - public void setSelected(@Nullable ModListEntry selected) { - this.selected = selected; - } - - @Override - protected boolean isSelected(int slotIndex) { - return Objects.equals(this.getSelected(), this.children.get(slotIndex)); - } - @Override protected void drawContainerBackground(@NotNull Tessellator tessellator) { if (this.mc.world != null) { @@ -592,6 +551,7 @@ public boolean mouseReleased(int mouseX, int mouseY, int button) { return super.mouseReleased(mouseX, mouseY, button); } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean shouldHideFavourites() { return this.hideFavourites; } @@ -638,7 +598,7 @@ private String formatQuery(String partial, int displayPos) { SEARCH_FILTER_VALUE + partial.substring(split + 1) + TextFormatting.RESET; } - private class ModListEntry implements CatalogueListExtended.IGuiListEntry { + private class ModListEntry implements CatalogueListExtended.IListEntry { private final IModData data; private final ModList list; private final PinnedButton button; @@ -806,8 +766,8 @@ public IModData getData() { } @Override - public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { - if (mouseEvent == 1) { + public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseButton, int relativeX, int relativeY) { + if (mouseButton == 1) { DropdownMenu menu = DropdownMenu.builder(CatalogueModListScreen.this) .setMinItemSize(0, 16) .setAlignment(DropdownMenu.Alignment.BELOW_LEFT) @@ -821,7 +781,7 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEven }).build(); menu.toggle(mouseX, mouseY); return true; - } else if (mouseEvent == 0) { + } else if (mouseButton == 0) { if (this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) return true; CatalogueModListScreen.this.setSelectedModData(this.data); this.list.setSelected(this); @@ -830,14 +790,6 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEven return false; } - @Override - public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { - } - - @Override - public void updatePosition(int slotIndex, int x, int y, float partialTicks) { - } - public boolean isMouseOver() { return this.hovered; } @@ -853,7 +805,7 @@ public PinnedButton(String modId) { } @Override - public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTick) { + public void drawButton(@NotNull Minecraft mc, int mouseX, int mouseY, float partialTick) { if (!this.visible) return; this.hovered = ModListEntry.this.isMouseOver() && ClientHelper.isMouseWithin(this.x, this.y, this.width, this.height, mouseX, mouseY); this.mouseDragged(mc, mouseX, mouseY); @@ -863,7 +815,7 @@ public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTick) } @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { + public boolean mousePressed(@NotNull Minecraft mc, int mouseX, int mouseY) { if (super.mousePressed(mc, mouseX, mouseY) && !ModListEntry.this.list.shouldHideFavourites()) { FAVOURITES.toggle(this.modId); ModListEntry.this.list.filterAndUpdateList(); @@ -960,8 +912,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { } } - private class StringList extends CatalogueListExtended { - private final List entries = Lists.newArrayList(); + private class StringList extends CatalogueListExtended { public StringList(int width, int height, int left, int top) { super(CatalogueModListScreen.this.mc, width, height, top, top + height, 10); @@ -970,7 +921,7 @@ public StringList(int width, int height, int left, int top) { } public void setTextFromInfo(@NotNull IModData data) { - this.entries.clear(); + this.clearEntries(); this.visible = true; if (data.getDescription() == null) return; if (data.getDescription().trim().isBlank()) { @@ -979,7 +930,7 @@ public void setTextFromInfo(@NotNull IModData data) { } List lines = CatalogueModListScreen.this.fontRenderer.listFormattedStringToWidth(data.getDescription().trim(), this.getListWidth()); for (String line : lines) { - this.entries.add(new StringEntry(line.replace("\n", "").replace("\r", "").trim())); + this.addEntry(new StringEntry(line.replace("\n", "").replace("\r", "").trim())); } } @@ -1010,27 +961,17 @@ public int getListWidth() { } @Override - protected int getRowTop(int pIndex) { - return super.getRowTop(pIndex) + 4; + protected int getRowTop(int slotIndex) { + return super.getRowTop(slotIndex) + 4; } @Override public int getMaxScroll() { return Math.max(0, this.getContentHeight() - (this.height - 12)); } - - @Override - protected int getSize() { - return this.entries.size(); - } - - @Override - public IGuiListEntry getListEntry(int index) { - return this.entries.get(index); - } } - private class StringEntry implements CatalogueListExtended.IGuiListEntry { + private class StringEntry implements CatalogueListExtended.IListEntry { private final String line; public StringEntry(String line) { @@ -1041,19 +982,6 @@ public StringEntry(String line) { public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { drawString(CatalogueModListScreen.this.fontRenderer, this.line, left, top, 0xFFFFFF); } - - @Override - public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { - return true; - } - - @Override - public void updatePosition(int slotIndex, int x, int y, float partialTicks) { - } - - @Override - public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { - } } /** @@ -1067,7 +995,7 @@ public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relat * @param y the y position * @param maxWidth the maximum width the string can render * @param mouseX the current mouse x position - * @param mouseY the current mouse u position + * @param mouseY the current mouse y position */ @SuppressWarnings("SameParameterValue") private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index 9668d9b66..737f42398 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -5,6 +5,7 @@ import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; +import org.jetbrains.annotations.NotNull; /** * Author: MrCrayfish @@ -35,7 +36,7 @@ public CatalogueIconButton(int id, int x, int y, int u, int v, int width, int he } @Override - public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks) { + public void drawButton(@NotNull Minecraft minecraft, int mouseX, int mouseY, float partialTicks) { // Draw bg super.drawButton(minecraft, mouseX, mouseY, partialTicks); // Draw icon and text diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java index 789fd38be..8bd94092a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListExtended.java @@ -8,14 +8,19 @@ import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.NotNull; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; -public abstract class CatalogueListExtended extends GuiListExtended { +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class CatalogueListExtended extends GuiListExtended { private boolean scrollBarVisible; - public CatalogueListExtended(Minecraft mcIn, int widthIn, int heightIn, int topIn, int bottomIn, int slotHeightIn) { - super(mcIn, widthIn, heightIn, topIn, bottomIn, slotHeightIn); + public CatalogueListExtended(Minecraft mc, int width, int height, int top, int bottom, int slotHeight) { + super(mc, width, height, top, bottom, slotHeight); } // Values renamed by deepseek. Comments are handwrite. @@ -47,7 +52,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawListHeader(this.getListLeft(), this.getListTop(), tessellator); } - this.drawSelectionBox(mouseX, mouseY, partialTicks); + this.renderListItems(mouseX, mouseY, partialTicks); GlStateManager.disableDepth(); GlStateManager.enableBlend(); @@ -108,41 +113,27 @@ protected void drawScrollBar(int maxScroll) { tessellator.draw(); } - protected void drawSelectionBox(int mouseX, int mouseY, float partialTicks) { - int size = this.getSize(); - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - for (int index = 0; index < size; ++index) { + protected void renderListItems(int mouseX, int mouseY, float partialTicks) { + for (int index = 0; index < this.getSize(); ++index) { + int rowLeft = this.getListLeft(); + int rowRight = this.getListRight(); int rowTop = this.getRowTop(index); int rowBottom = this.getRowBottom(index) - 4; if (rowTop > this.bottom || rowBottom < this.top) { - this.updateItemPos(index, this.getListLeft(), rowTop, partialTicks); + this.updateItemPos(index, rowLeft, rowTop, partialTicks); } - if (this.showSelectionBox && this.isSelected(index)) { - int left = this.getListLeft(); - int right = this.getListRight(); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.disableTexture2D(); - buffer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); - buffer.pos(left, rowBottom + 2, 0.0D).tex(0.0D, 1.0D).color(128, 128, 128, 255).endVertex(); - buffer.pos(right, rowBottom + 2, 0.0D).tex(1.0D, 1.0D).color(128, 128, 128, 255).endVertex(); - buffer.pos(right, rowTop - 2, 0.0D).tex(1.0D, 0.0D).color(128, 128, 128, 255).endVertex(); - buffer.pos(left, rowTop - 2, 0.0D).tex(0.0D, 0.0D).color(128, 128, 128, 255).endVertex(); - buffer.pos(left + 1, rowBottom + 1, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos(right - 1, rowBottom + 1, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos(right - 1, rowTop - 1, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos(left + 1, rowTop - 1, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 255).endVertex(); - tessellator.draw(); - GlStateManager.enableTexture2D(); + if (rowTop + this.slotHeight >= this.top && rowTop <= this.bottom) { + this.renderItem(index, rowLeft, rowTop, rowRight, rowBottom, mouseX, mouseY, partialTicks); } - - this.drawSlot(index, this.getListLeft(), rowTop, rowBottom - rowTop, mouseX, mouseY, partialTicks); } } + protected void renderItem(int slotIndex, int rowLeft, int rowTop, int rowRight, int rowBottom, int mouseX, int mouseY, float partialTicks) { + this.drawSlot(slotIndex, rowLeft, rowTop, rowBottom - rowTop, mouseX, mouseY, partialTicks); + } + @Override public void handleMouseInput() { if (this.isMouseYWithinSlotBounds(this.mouseY)) { @@ -227,8 +218,7 @@ public void handleMouseInput() { @Deprecated @Override - protected void drawSelectionBox(int contentLeft, int contentTop, int mouseXIn, int mouseYIn, float partialTicks) { - this.drawSelectionBox(mouseXIn, mouseYIn, partialTicks); + protected void drawSelectionBox(int contentLeft, int contentTop, int mouseX, int mouseY, float partialTicks) { } @Override @@ -250,13 +240,13 @@ public boolean mouseClicked(int mouseX, int mouseY, int mouseEvent) { } @Override - public boolean mouseReleased(int x, int y, int mouseEvent) { + public boolean mouseReleased(int mouseX, int mouseY, int mouseEvent) { for (int slotIndex = 0; slotIndex < this.getSize(); ++slotIndex) { int j = this.left + this.getListLeft(); int k = this.top + 4 - this.getAmountScrolled() + slotIndex * this.slotHeight + this.headerPadding; - int relativeX = x - j; - int relativeY = y - k; - this.getListEntry(slotIndex).mouseReleased(slotIndex, x, y, mouseEvent, relativeX, relativeY); + int relativeX = mouseX - j; + int relativeY = mouseY - k; + this.getListEntry(slotIndex).mouseReleased(slotIndex, mouseX, mouseY, mouseEvent, relativeX, relativeY); } this.setEnabled(true); return false; @@ -311,11 +301,97 @@ protected int getListTop() { return this.top + 4 - (int) this.amountScrolled; } - protected int getRowTop(int pIndex) { - return this.top + 4 - (int) this.amountScrolled + pIndex * this.slotHeight + this.headerPadding; + protected int getRowTop(int slotIndex) { + return this.top + 4 - (int) this.amountScrolled + slotIndex * this.slotHeight + this.headerPadding; + } + + protected int getRowBottom(int slotIndex) { + return this.getRowTop(slotIndex) + this.slotHeight; + } + + /* + Some helpers. + */ + + private final List children = new ArrayList<>(); + + public final List children() { + return this.children; + } + + @NotNull + @Override + public E getListEntry(int slotIndex) { + return this.children().get(slotIndex); } - protected int getRowBottom(int pIndex) { - return this.getRowTop(pIndex) + this.slotHeight; + @Override + protected int getSize() { + return this.children().size(); + } + + public void centerScrollOn(E pEntry) { + this.setAmountScrolled((float) (this.children().indexOf(pEntry) * this.slotHeight + this.slotHeight / 2 - (this.bottom - this.top) / 2)); + } + + public void addEntry(E pEntry) { + this.children().add(pEntry); + } + + public void clearEntries() { + this.children().clear(); + } + + public void replaceEntries(Collection entries) { + this.clearEntries(); + this.children().addAll(entries); + } + + public void removeEntries(@NotNull List entries) { + entries.forEach(this::removeEntry); + } + + public void removeEntry(E entry) { + this.children.remove(entry); + } + + public void clearEntriesExcept(E entry) { + this.children.removeIf((e) -> e != entry); + } + + public interface IListEntry extends IGuiListEntry { + + /** + * Called when the entry's position is moved. + */ + @Override + default void updatePosition(int slotIndex, int x, int y, float partialTicks) { + } + + /** + * Called when the mouse is clicked within this entry. + * + * @param mouseX the current mouse x position + * @param mouseY the current mouse y position + * @param relativeX the current x position of the mouse relative to the top-left corner of the entry + * @param relativeY the current y position of the mouse relative to the top-left corner of the entry + * @return true means that something within this entry was clicked and the list should not be dragged. + */ + @Override + default boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseButton, int relativeX, int relativeY) { + return true; + } + + /** + * Called when the mouse button is released. + * + * @param mouseX the current mouse x position + * @param mouseY the current mouse y position + * @param relativeX the current x position of the mouse relative to the top-left corner of the entry + * @param relativeY the current y position of the mouse relative to the top-left corner of the entry + */ + @Override + default void mouseReleased(int slotIndex, int mouseX, int mouseY, int mouseButton, int relativeX, int relativeY) { + } } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListSelection.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListSelection.java new file mode 100644 index 000000000..21305fdd2 --- /dev/null +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListSelection.java @@ -0,0 +1,66 @@ +package com.cleanroommc.catalogue.client.screen.widget; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class CatalogueListSelection extends CatalogueListExtended { + private @Nullable E selected; + + public CatalogueListSelection(Minecraft mcIn, int widthIn, int heightIn, int topIn, int bottomIn, int slotHeightIn) { + super(mcIn, widthIn, heightIn, topIn, bottomIn, slotHeightIn); + } + + @Override + public void clearEntries() { + super.clearEntries(); + this.setSelected(null); + } + + @Override + protected boolean isSelected(int slotIndex) { + return Objects.equals(this.getSelected(), this.getListEntry(slotIndex)); + } + + @Nullable + public E getSelected() { + return this.selected; + } + + public void setSelected(@Nullable E selected) { + this.selected = selected; + } + + @Override + public void removeEntry(E entry) { + if (this.children().remove(entry) && entry == this.getSelected()) { + this.setSelected(null); + } + } + + @Override + public void clearEntriesExcept(E entry) { + super.clearEntriesExcept(entry); + if (this.selected != entry) { + this.setSelected(null); + } + } + + @Override + protected void renderItem(int slotIndex, int rowLeft, int rowTop, int rowRight, int rowBottom, int mouseX, int mouseY, float partialTicks) { + if (this.showSelectionBox && this.isSelected(slotIndex)) { + renderSelection(rowLeft, rowTop, rowRight, rowBottom); + } + super.renderItem(slotIndex, rowLeft, rowTop, rowRight, rowBottom, mouseX, mouseY, partialTicks); + } + + @SuppressWarnings("SameParameterValue") + protected void renderSelection(int left, int top, int right, int bottom) { + top -= 2; + bottom += 2; + Gui.drawRect(left, top, right, bottom, 0xFF808080); + Gui.drawRect(left + 1, top + 1, right - 1, bottom - 1, -16777216); + } +} diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java index cf43306ea..4bd85331a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java @@ -7,6 +7,7 @@ import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.NotNull; import org.lwjgl.opengl.GL11; public class CatalogueTextButton extends GuiButton { @@ -16,7 +17,7 @@ public CatalogueTextButton(int buttonId, int x, int y, int widthIn, int heightIn super(buttonId, x, y, widthIn, heightIn, buttonText); } - public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTicks) { + public void drawButton(@NotNull Minecraft mc, int mouseX, int mouseY, float partialTicks) { if (!this.visible) return; this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java index b57989cef..84e229f3d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java @@ -131,7 +131,7 @@ public void setMaxStringLength(int length) { // Call consumer responder @Override - public void setResponderEntryValue(int idIn, String textIn) { + public void setResponderEntryValue(int idIn, @NotNull String textIn) { if (this.responder != null) { this.responder.accept(textIn); } @@ -143,10 +143,12 @@ public void setSuggestion(@NotNull String suggestion) { this.suggestion = suggestion; } + @NotNull public String getSuggestion() { return this.suggestion; } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isTextTruncated() { return this.isTextTruncated; } From f5ffc1cc61851a8902a914022ae39c80086e4e70 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 6 Dec 2025 15:15:07 +0800 Subject: [PATCH 64/91] Update CatalogueModListScreen.java --- .../catalogue/client/screen/CatalogueModListScreen.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 2a9bb48e2..e221a7717 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -545,9 +545,7 @@ public void handleMouseInput() { @Override public boolean mouseReleased(int mouseX, int mouseY, int button) { - if (this.hideFavourites) { - this.hideFavourites = false; - } + this.hideFavourites = false; return super.mouseReleased(mouseX, mouseY, button); } From 1421313feb16ee5ebabf72d122c697c17b7c06e2 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 7 Dec 2025 13:00:15 +0800 Subject: [PATCH 65/91] Update forge_at.cfg --- src/main/resources/forge_at.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/forge_at.cfg b/src/main/resources/forge_at.cfg index 8760784b6..aaa51ab58 100644 --- a/src/main/resources/forge_at.cfg +++ b/src/main/resources/forge_at.cfg @@ -225,7 +225,6 @@ protected net.minecraft.client.gui.GuiTextField field_146223_s # selectionEnd - protected net.minecraft.client.gui.GuiTextField field_146224_r # cursorPosition - needed for mod list GUI stuff protected net.minecraft.client.gui.GuiTextField field_146225_q # lineScrollOffset - needed for mod list GUI stuff protected net.minecraft.client.gui.GuiTextField field_146226_p # isEnabled - needed for mod list GUI stuff -protected net.minecraft.client.gui.GuiTextField field_175209_y # validator - needed for mod list GUI stuff protected net.minecraft.client.gui.GuiTextField func_146188_c(IIII)V # drawSelectionBox - needed for mod list GUI stuff # GuiSlot public net.minecraft.client.gui.GuiSlot field_148149_f # slotHeight - needed for config GUI stuff From 8c1eca9ffc8780d6edad64c791198bcc937a9f16 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 13 Dec 2025 01:31:44 +0800 Subject: [PATCH 66/91] Fix some render code --- .../client/screen/CatalogueModListScreen.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index e221a7717..6cc1c2a73 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -655,10 +655,15 @@ private void drawIcon(int top, int left) { return; } try { + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableDepth(); + GlStateManager.enableRescaleNormal(); RenderHelper.enableGUIStandardItemLighting(); - CatalogueModListScreen.this.mc.getRenderItem().renderItemIntoGUI(this.icon, left + 4, top + 2); + CatalogueModListScreen.this.itemRender.renderItemAndEffectIntoGUI(this.icon, left + 4, top + 2); + CatalogueModListScreen.this.itemRender.renderItemOverlays(CatalogueModListScreen.this.fontRenderer, this.icon, left + 4, top + 2); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.disableDepth(); + GlStateManager.disableRescaleNormal(); RenderHelper.disableStandardItemLighting(); } catch (Exception e) { // Attempt to catch exceptions when rendering item. Sometime level instance isn't checked for null @@ -808,8 +813,11 @@ public void drawButton(@NotNull Minecraft mc, int mouseX, int mouseY, float part this.hovered = ModListEntry.this.isMouseOver() && ClientHelper.isMouseWithin(this.x, this.y, this.width, this.height, mouseX, mouseY); this.mouseDragged(mc, mouseX, mouseY); int textureU = FAVOURITES.has(this.modId) ? 10 : 0; + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); mc.getTextureManager().bindTexture(TEXTURE); drawModalRectWithCustomSizedTexture(this.x, this.y, textureU, 10, 10, 10, 64, 64); + GlStateManager.disableBlend(); } @Override From 9a2727e15d8474d5f6da7e341254eca09eb39863 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 20 Dec 2025 01:50:43 +0800 Subject: [PATCH 67/91] Optimize item pick, remove item overlay layer --- .../client/screen/CatalogueModListScreen.java | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 6cc1c2a73..c39a3cfc9 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -19,6 +19,7 @@ import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.resources.I18n; +import net.minecraft.creativetab.CreativeTabs; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -660,7 +661,6 @@ private void drawIcon(int top, int left) { GlStateManager.enableRescaleNormal(); RenderHelper.enableGUIStandardItemLighting(); CatalogueModListScreen.this.itemRender.renderItemAndEffectIntoGUI(this.icon, left + 4, top + 2); - CatalogueModListScreen.this.itemRender.renderItemOverlays(CatalogueModListScreen.this.fontRenderer, this.icon, left + 4, top + 2); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.disableDepth(); GlStateManager.disableRescaleNormal(); @@ -709,22 +709,39 @@ private void drawIcon(int top, int left) { } } - // If the mod doesn't specify an item to use, Catalogue will attempt to get an item from the mod - Optional optional = ForgeRegistries.ITEMS.getValuesCollection().stream().filter(item -> Objects.requireNonNull(item.getRegistryName()).getNamespace().equals(this.data.getModId())).map(ItemStack::new).findFirst(); - if (optional.isPresent()) { - ItemStack item = optional.get(); - if (!item.isEmpty()) { - // If the item is in a creative tab, Catalogue will attempt to use the tab's icon - if (item.getItem().getCreativeTab() != null) { + // If the mod has a creative tab, Catalogue will attempt to use the tab's icon + Optional optional = Arrays.stream(CreativeTabs.CREATIVE_TAB_ARRAY) + .filter(Objects::nonNull) + .map(tab -> { try { - ItemStack tabItem = item.getItem().getCreativeTab().getIcon(); - if (tabItem != null && !tabItem.isEmpty() && Objects.requireNonNull(tabItem.getItem().getRegistryName()).getNamespace().equals(this.data.getModId())) { - item = tabItem; - } + return tab.getIcon(); } catch (Exception e) { CatalogueConstants.LOG.debug("Failed to get creative tab icon for mod '{}'", this.data.getModId(), e); + return ItemStack.EMPTY; } - } + }) + .filter(tabItem -> !tabItem.isEmpty()) + .filter(tabItem -> { + ResourceLocation resource = tabItem.getItem().getRegistryName(); + return resource != null && resource.getNamespace().equals(this.data.getModId()); + }) + .findFirst(); + + // If the mod doesn't specify an item to use, Catalogue will attempt to get an item from the mod + if (!optional.isPresent()) { + optional = ForgeRegistries.ITEMS.getValuesCollection().stream() + .filter(Objects::nonNull) + .filter(item -> { + ResourceLocation resource = item.getRegistryName(); + return resource != null && resource.getNamespace().equals(this.data.getModId()); + }) + .map(ItemStack::new) + .findFirst(); + } + + if (optional.isPresent()) { + ItemStack item = optional.get(); + if (!item.isEmpty()) { ITEM_ICON_CACHE.put(this.data.getModId(), item); return item; } From 20df0d00ae38093c1497037df028fe070b7be008 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 20 Dec 2025 10:39:15 +0800 Subject: [PATCH 68/91] Update build.gradle --- build.gradle | 989 --------------------------------------------------- 1 file changed, 989 deletions(-) diff --git a/build.gradle b/build.gradle index e18537d99..7b2f23e6a 100644 --- a/build.gradle +++ b/build.gradle @@ -49,995 +49,6 @@ println "Java: ${ -> sysProps['java.version']} | JVM: ${ -> sysProps['java.vm.ve // Initialize buildSrc Util.init() -// Post-Processing -def post_processor = [ - tool: 'net.minecraftforge:mcpcleanup:2.3.2:fatjar', - repo: 'https://maven.minecraftforge.net/', - args: ['--input', '{input}', '--output', '{output}'] -] - -def jvm_arguments = [ - '--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED', - '--add-opens=java.base/jdk.internal.reflect=ALL-UNNAMED', - '--add-opens=java.base/java.lang=ALL-UNNAMED', - '--add-opens=java.base/java.lang.reflect=ALL-UNNAMED' -] - -def compiler_jvm_arguments = [ - '--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED', - '--add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED' -] -// Projects - -// MCP -project(':mcp') { - apply plugin: 'net.minecraftforge.gradle.mcp' - repositories { - maven { - name "outlandsReleases" - url "https://maven.outlands.top/releases" - } - maven { - name 'MinecraftForge' - url 'https://maven.minecraftforge.net/' - } - - } - mcp { - config = props.minecraft_version + '-' + props.mcp_version - pipeline = 'joined' - } -} - -// Minecraft -project(':minecraft') { - evaluationDependsOn(':mcp') - apply plugin: 'eclipse' - apply plugin: 'net.minecraftforge.gradle.patcher' - - repositories { - mavenLocal() - mavenCentral() - maven { - name "outlandsReleases" - url "https://maven.outlands.top/releases" - } - maven { - name 'MinecraftForge' - url 'https://maven.minecraftforge.net/' - } - - } - dependencies { - implementation ('net.minecraftforge:mergetool:0.2.3.3:forge') { - exclude group: 'org.ow2.asm', module: 'asm-tree' - exclude group: 'org.ow2.asm', module: 'asm-util' - exclude group: 'org.ow2.asm', module: 'asm' - } - } - - patcher { - parent = project(':mcp') - mcVersion = props.minecraft_version - patchedSrc = file('src/main/java') - - mappings channel: props.mapping_channel, version: props.mapping_version - processor = post_processor - - runs { - minecraftClient { - client true - taskName 'minecraftClient' - ideaModule "${rootProject.name}.${project.name}.main" - - main 'net.minecraft.client.main.Main' - workingDirectory project.file('run') - - args '--gameDir', '.' - args '--version', props.minecraft_version - args '--assetsDir', downloadAssets.output - args '--assetIndex', '{asset_index}' - args '--accessToken', '0' - } - - minecraftServer { - client false - taskName 'minecraftServer' - ideaModule "${rootProject.name}.${project.name}.main" - - main 'net.minecraft.server.MinecraftServer' - workingDirectory project.file('run') - } - } - } -} - -// Cleanroom -project(':cleanroom') { - evaluationDependsOn(':minecraft') - apply plugin: 'java-library' - apply plugin: 'maven-publish' - apply plugin: 'net.minecraftforge.gradle.patcher' - - // = 'net.minecraftforge' - group = 'com.cleanroommc' - version = rootProject.version - - compileJava { - doFirst { - def target = new File("${rootProject.projectDir}/src/main/java/com/cleanroommc/common/CleanroomVersion.java") - def template = new File("${rootProject.projectDir}/templates/CleanroomVersion.java") - - target.withWriter { def writer -> - template.eachLine { def line -> - def newLine = line.replace("%VERSION%", versionDetails().lastTag) - .replace("%BUILD_VERSION%", rootProject.version.toString()) - writer.write(newLine + "\n"); - } - } - } - } - - sourceSets { - main { - java { - srcDirs = ["$rootDir/src/main/java"] - } - resources { - srcDirs = ["$rootDir/src/main/resources"] - } - } - test { - compileClasspath += sourceSets.main.runtimeClasspath - runtimeClasspath += sourceSets.main.runtimeClasspath - java { - srcDirs = ["$rootDir/src/test/java"] - } - resources { - srcDirs = ["$rootDir/src/test/resources"] - } - } - userdev { - compileClasspath += sourceSets.main.runtimeClasspath - runtimeClasspath += sourceSets.main.runtimeClasspath - } - userdevTest { - compileClasspath += sourceSets.userdev.runtimeClasspath - runtimeClasspath += sourceSets.userdev.runtimeClasspath - compileClasspath += sourceSets.test.runtimeClasspath - runtimeClasspath += sourceSets.test.runtimeClasspath - } - } - - repositories { - mavenLocal() - mavenCentral() - maven { - name 'Cleanroom Maven' - url 'https://maven.cleanroommc.com/' - } - maven { - url "https://repo.cleanroommc.com/snapshots" - } - maven { - url "https://maven.outlands.top/releases/" - } - } - - ext { - version_json = project(':mcp').file('build/mcp/downloadJson/version.json') - lwjglArch = Util.getCurrentArch() - // Do not change the order unless it is required so - lwjglLibraries = [ - [ - 'lwjgl-glfw', 'lwjgl-jemalloc', 'lwjgl-openal', - 'lwjgl-opengl', 'lwjgl-stb', 'lwjgl-tinyfd', 'lwjgl' - ], - - [ - 'natives-linux-arm64', 'natives-linux-arm32', 'natives-linux', - 'natives-macos-arm64', 'natives-macos', - 'natives-windows-arm64', 'natives-windows-x86', 'natives-windows' - ] - ] - } - - patcher { - excs.from file("$rootDir/src/main/resources/forge.exc") - parent = project(':minecraft') - patches = file("$rootDir/patches/minecraft") - patchedSrc = file('src/main/java') - srgPatches = true - notchObf = true - accessTransformer = file("$rootDir/src/main/resources/forge_at.cfg") - // sideAnnotationStripper = file("$rootDir/src/main/resources/forge.sas") - processor = post_processor - - runs { - - cleanroomClient { - client = true - taskName 'cleanroomClient' - ideaModule "${rootProject.name}.${project.name}.main" - main 'com.cleanroommc.boot.MainClient' - workingDirectory project.file('run') - - environment 'target', 'fmldevclient' - environment 'tweakClass', 'net.minecraftforge.fml.common.launcher.FMLTweaker' - environment 'mainClass', 'top.outlands.foundation.boot.Foundation' - environment 'assetIndex', '{asset_index}' - environment 'assetDirectory', downloadAssets.output - environment 'nativesDirectory', extractNatives.output.get().asFile - environment 'MC_VERSION', props.minecraft_version - environment 'MCP_VERSION', props.mcp_version - environment 'MCP_MAPPINGS', '{mcp_mappings}' - environment 'MCP_TO_SRG', createSrg2Mcp.getOutput().get().getAsFile().getAbsolutePath() - environment 'FORGE_GROUP', project.group - environment 'FORGE_VERSION', props.last_forge_version - - jvmArgs jvm_arguments + '-Dmixin.debug.export=true' + '-Dmixin.checks.interfaces=true' - //jvmArgs jvm_arguments - - // Lazily supply the Mappings target, createSrg2Mcp.getMappings() doesn't get populated until later - lazyToken 'mcp_mappings', { -> - createSrg2Mcp.getMappings().get() - } - - mods { - cleanroom { - source sourceSets.main - } - } - } - - cleanroomTestClient { - parent runs.cleanroomClient - taskName 'cleanroomTestClient' - - environment 'MOD_CLASSES', 'dummy' // Needed to work around FG limitation, FG will replace this! - - ideaModule "${rootProject.name}.${project.name}.userdevTest" - - mods { - cleanroom { - source sourceSets.main - } - tests { - sources sourceSets.test - } - } - } - - cleanroomServer { - client false - taskName 'cleanroomServer' - ideaModule "${rootProject.name}.${project.name}.main" - main 'com.cleanroommc.boot.MainServer' - workingDirectory project.file('run') - - environment 'target', 'fmldevserver' - environment 'tweakClass', 'net.minecraftforge.fml.common.launcher.FMLServerTweaker' - environment 'mainClass', 'top.outlands.foundation.boot.Foundation' - environment 'MC_VERSION', props.minecraft_version - environment 'MCP_VERSION', props.mcp_version - environment 'MCP_MAPPINGS', '{mcp_mappings}' - environment 'MCP_TO_SRG', createSrg2Mcp.getOutput().get().getAsFile().getAbsolutePath() - environment 'FORGE_GROUP', project.group - environment 'FORGE_VERSION', props.last_forge_version - - // Lazily supply the Mappings target, createSrg2Mcp.getMappings() doesn't get populated until later - lazyToken 'mcp_mappings', { -> - createSrg2Mcp.getMappings().get() - } - - mods { - cleanroom { - source sourceSets.main - } - } - } - - cleanroomTestServer { - parent runs.cleanroomServer - taskName 'cleanroomTestServer' - - environment 'MOD_CLASSES', 'dummy' // Needed to work around FG limitation, FG will replace this! - - ideaModule "${rootProject.name}.${project.name}.userdevTest" - - mods { - cleanroom { - source sourceSets.main - } - tests { - sources sourceSets.test - } - } - } - } - - } - - // Patches - applyPatches { - originalPrefix = 'before/' - modifiedPrefix = 'after/' - printSummary = true - } - - genPatches { - originalPrefix = 'before/' - modifiedPrefix = 'after/' - } - - // Dependencies - configurations { - configureEach { // LWJGL2 isn't friendly with us, so pye pye - exclude group: 'org.lwjgl.lwjgl' - exclude group: 'com.ibm.icu', module: 'icu4j-core-mojang' - /*exclude group: 'org.apache.commons' - exclude group: 'org.apache.httpcomponents' - exclude group: 'it.unimi.dsi' - exclude group: 'oshi-project' - exclude group: 'commons-codec' - exclude group: 'commons-io' - exclude group: 'commons-logging' - exclude group: 'com.google.code.findbugs' - exclude group: 'com.google.code.gson' - exclude group: 'com.google.guava'*/ - } - - // Don't pull all libraries, if we're missing something, add it to the installer list so the installer knows to download it. - installer { - transitive = false - } - api.extendsFrom(installer) - - lwjglNatives { - transitive = false - } - - testCompile.extendsFrom(lwjglNatives) - } - - dependencies { - compileOnly "com.cleanroommc:lwjglx:1.0.0" - compileOnly "org.jetbrains:annotations:24.0.0" - installer "com.cleanroommc:lwjglxx:1.1.5" - lwjglLibraries[0].each { - installer "org.lwjgl:$it:$props.lwjgl_version" - runtimeOnly "org.lwjgl:$it::$lwjglArch" - - lwjglLibraries[1].each { arch -> - lwjglNatives "org.lwjgl:$it:$props.lwjgl_version:$arch" - } - } - - // TODO: ASM 9.4 required, breaks current Event System when creating classes on-the-fly - installer "org.ow2.asm:asm:$props.asm_version" - installer "org.ow2.asm:asm-commons:$props.asm_version" - installer "org.ow2.asm:asm-tree:$props.asm_version" - installer "org.ow2.asm:asm-util:$props.asm_version" - installer "org.ow2.asm:asm-analysis:$props.asm_version" - // Added back deprecated features for mods to load up properly - installer "org.ow2.asm:asm-deprecated:$props.asm_deprecated" - - installer "top.outlands:foundation:0.15.4" - installer 'net.lenni0451:Reflect:1.5.0' - installer 'org.javassist:javassist:3.30.2-GA' - installer "zone.rong:imaginebreaker:2.1" - - installer 'com.ibm.icu:icu4j:77.1' - - installer 'org.jline:jline:3.29.0' - installer 'org.jline:jline-native:3.29.0' - - installer 'net.java.jinput:jinput:2.0.10' - - installer 'lzma:lzma:0.0.1' - installer 'java3d:vecmath:1.5.2' - installer 'net.sf.trove4j:core:3.1.0' - installer 'net.sf.trove4j:experimental:3.1.0' - installer 'org.apache.maven:maven-artifact:3.9.9' - installer 'net.sf.jopt-simple:jopt-simple:5.0.4' - installer 'org.apache.commons:commons-lang3:3.17.0' - installer 'org.apache.commons:commons-compress:1.27.1' - installer 'org.apache.httpcomponents.core5:httpcore5:5.3.4' - installer 'org.apache.httpcomponents.core5:httpcore5-h2:5.3.4' - installer 'org.apache.httpcomponents.client5:httpclient5:5.5' - installer 'com.github.oshi:oshi-core:6.8.2' - installer 'net.java.dev.jna:jna:5.17.0' - installer 'net.java.dev.jna:jna-platform:5.17.0' - installer 'it.unimi.dsi:fastutil:8.5.16' - installer 'commons-codec:commons-codec:1.18.0' - installer 'commons-io:commons-io:2.19.0' - installer 'commons-logging:commons-logging:1.3.5' - installer 'com.google.guava:guava:33.4.8-jre' - installer 'com.google.guava:failureaccess:1.0.2' - installer 'com.google.code.gson:gson:2.13.1' - installer 'com.google.code.findbugs:jsr305:3.0.2' - installer 'org.jspecify:jspecify:1.0.0' - - // Netty - installer "io.netty:netty-codec-base:$props.netty_version" - installer "io.netty:netty-buffer:$props.netty_version" - installer "io.netty:netty-common:$props.netty_version" - installer "io.netty:netty-handler:$props.netty_version" - installer "io.netty:netty-resolver:$props.netty_version" - installer "io.netty:netty-transport:$props.netty_version" - installer "io.netty:netty-transport-native-unix-common:$props.netty_version:linux-x86_64" - installer "io.netty:netty-transport-classes-epoll:$props.netty_version" - installer "io.netty:netty-transport-native-epoll:$props.netty_version:linux-x86_64" - installer "io.netty:netty-codec-http:$props.netty_version" - installer "io.netty:netty-codec-http2:$props.netty_version" - installer "io.netty:netty-codec-dns:$props.netty_version" - installer "io.netty:netty-codec-compression:$props.netty_version" - installer "io.netty:netty-resolver-dns:$props.netty_version" - - installer 'org.apache.logging.log4j:log4j-api:2.25.1' - installer 'org.apache.logging.log4j:log4j-core:2.25.1' - installer 'org.apache.logging.log4j:log4j-slf4j2-impl:2.25.1' - installer 'org.slf4j:slf4j-api:2.0.17' - installer 'jakarta.annotation:jakarta.annotation-api:3.0.0' - installer 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.2' - installer 'org.glassfish.jaxb:jaxb-runtime:4.0.5' - installer 'org.glassfish.jaxb:jaxb-core:4.0.5' - installer 'com.sun.istack:istack-commons-runtime:4.2.0' - installer 'jakarta.xml.ws:jakarta.xml.ws-api:4.0.2' - installer 'jakarta.activation:jakarta.activation-api:2.1.3' - installer 'org.glassfish.corba:glassfish-corba-omgapi:4.2.5' - installer 'org.openjdk.nashorn:nashorn-core:15.6' - // installer 'ca.weblite:java-objc-bridge:1.2' - - - // Mixin - installer 'com.cleanroommc:sponge-mixin:0.20.10+mixin.0.8.7' - installer annotationProcessor('io.github.llamalad7:mixinextras-common:0.5.0') - - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.12.0' - testImplementation 'org.junit.vintage:junit-vintage-engine:5.12.0' - testImplementation 'org.opentest4j:opentest4j:1.3.0' // Needed for junit 5 - testImplementation 'org.hamcrest:hamcrest:3.0' // Needs advanced matching for list order - - - } - - // TODO: Include? - def extra_files = [ - rootProject.file('CREDITS.txt'), - rootProject.file('LICENSE.txt'), - rootProject.file('LICENSE-Paulscode IBXM Library.txt'), - rootProject.file('LICENSE-Paulscode SoundSystem CodecIBXM.txt') - ] - - def changelog = rootProject.file('build/changelog.txt') - if (changelog.exists()) { - extra_files += changelog - } - - // Binary Patching - - // We apply the bin patches we just created to make a jar that is JUST our changes - ['Client', 'Server', 'Joined'].each { side -> - def gen = tasks.getByName("gen${side}BinPatches") - gen.tool = props.binary_patcher - tasks.register("apply${side}BinPatches", ApplyBinPatches) { - dependsOn gen - clean = gen.cleanJar - patch = gen.output - tool = props.binary_patcher - } - } - - tasks.register('genRuntimeBinPatches', GenerateBinPatches) { - dependsOn genClientBinPatches, genServerBinPatches - tool = props.binary_patcher - } - - afterEvaluate { p -> - genRuntimeBinPatches { - cleanJar = genClientBinPatches.cleanJar - dirtyJar = genClientBinPatches.dirtyJar - srg = genClientBinPatches.srg - patchSets.setFrom(genClientBinPatches.patchSets) - getArgs().set([ - '--output', '{output}', - '--patches', '{patches}', - '--srg', '{srg}', - // '--legacy', - '--clean', '{clean}', - '--dirty', '{dirty}', - '--prefix', 'binpatch/client', - '--clean', genServerBinPatches.cleanJar.get().asFile.path, - '--dirty', genServerBinPatches.dirtyJar.get().asFile.path, - '--prefix', 'binpatch/server', - ]) - } - } - - tasks.register('downloadLibraries') { - dependsOn ':mcp:setupMCP' - inputs.file version_json - doLast { - def json = version_json.json() - json.libraries.each { lib -> - def artifacts = [lib.downloads.artifact] + lib.downloads.get('archiveClassifier', [: ]).values() - artifacts.each { art -> - def target = file('build/libraries/' + art.path) - if (!target.exists()) { - download { - src art.url - dest target - } - } - } - } - } - } - - tasks.register('extractInheritance', ExtractInheritance) { - dependsOn genJoinedBinPatches, downloadLibraries - input = genJoinedBinPatches.cleanJar - doFirst { - def json = version_json.json() - json.libraries.each { lib -> - def artifacts = [lib.downloads.artifact] + lib.downloads.get('archiveClassifier', [: ]).values() - artifacts.each { art -> - def target = file('build/libraries/' + art.path) - if (target.exists()) { - addLibrary(target) - } - } - } - } - } - - tasks.register('checkAccessTransformers') { - dependsOn genJoinedBinPatches - inputs.file { - genJoinedBinPatches.cleanJar - } - inputs.files patcher.accessTransformers - doLast { - def vanilla = [:] - def zip = new ZipFile(genJoinedBinPatches.cleanJar.get().asFile) - zip.entries().findAll { - !it.directory && it.name.endsWith('.class') - }.each { entry -> - new ClassReader(zip.getInputStream(entry)).accept(new ClassVisitor(Opcodes.ASM9) { - - String name - - void visit(int version, int access, String name, String sig, String superName, String[] interfaces) { - this.name = name - vanilla[name] = access - } - - FieldVisitor visitField(int access, String name, String desc, String sig, Object value) { - vanilla[this.name + ' ' + name] = access - return null - } - - MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] excs) { - vanilla[this.name + ' ' + name + desc] = access - return null - } - - }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) - } - patcher.accessTransformers.each { f -> - TreeMap lines = [:] - f.eachLine { line -> - def idx = line.indexOf('#') - if (idx == 0 || line.isEmpty()) { - return - } - def comment = idx == -1 ? null : line.substring(idx) - if (idx != -1) { - line = line.substring(0, idx - 1) - } - def (modifier, cls, desc) = (line.trim() + ' ').split(' ', -1) - def key = cls + (desc.isEmpty() ? '' : ' ' + desc) - def access = vanilla[key.replace('.', '/')] - if (access == null) { - if ((desc == '*' || desc == '*()') && vanilla[cls.replace('.', '/')] != null) { - println('Warning: ' + line) - } else { - println('Invalid: ' + line) - return - } - } - // TODO: Check access actually changes, and expand inheritance? - lines[key] = [modifier: modifier, comment: comment] - } - f.text = lines.collect { it.value.modifier + ' ' + it.key + (it.value.comment == null ? '' : ' ' + it.value.comment) }.join('\n') - } - } - } - - tasks.register('checkSAS') { - dependsOn extractInheritance - inputs.file { - extractInheritance.output - } - inputs.files patcher.sideAnnotationStrippers - doLast { - def json = extractInheritance.output.json() - patcher.sideAnnotationStrippers.each { f -> - def lines = [] - f.eachLine { line -> - if (line[0] == '\t') { - return // Skip any tabbed lines, those are ones we add - } - def idx = line.indexOf('#') - if (idx == 0 || line.isEmpty()) { - lines.add(line) - return - } - def comment = idx == -1 ? null : line.substring(idx) - if (idx != -1) line = line.substring(0, idx - 1) - - def (cls, desc) = (line.trim() + ' ').split(' ', -1) - cls = cls.replaceAll('\\.', '/') - desc = desc.replace('(', ' (') - if (desc.isEmpty() || json[cls] == null || json[cls]['methods'] == null || json[cls]['methods'][desc] == null) { - println('Invalid: ' + line) - return - } - def mtd = json[cls]['methods'][desc] - lines.add(cls + ' ' + desc.replace(' ', '') + (comment == null ? '' : ' ' + comment)) - def children = json.values().findAll { it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls } - .collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet - children.each { lines.add('\t' + it) } - } - f.text = lines.join('\n') - } - } - } - - tasks.register('launcherJson') { - dependsOn universalJar - inputs.file { - universalJar.archivePath - } - ext { - output = file('build/version.json') - vanilla = project(':mcp').file('build/mcp/downloadJson/version.json') - timestamp = Util.iso8601Now() - comment = [ - 'Please do not automate the download and installation of Cleanroom.', - 'Our efforts are supported by ads from the download page.' - // TODO: Donation Page? (Was LexManos') - ] - id = "${project.name}-${project.version}" - } - inputs.file vanilla - outputs.file output - doLast { - def json_vanilla = vanilla.json() - def json = [ - _comment_: comment, - id: id, - time: timestamp, - releaseTime: timestamp, - type: 'release', - mainClass: 'top.outlands.foundation.boot.Foundation', - inheritsFrom: props.minecraft_version, - logging: { }, - minecraftArguments: [ - '--username', '${auth_player_name}', - '--version', '${version_name}', - '--gameDir', '${game_directory}', - '--assetsDir', '${assets_root}', - '--assetIndex', '${assets_index_name}', - '--uuid', '${auth_uuid}', - '--accessToken', '${auth_access_token}', - '--userType', '${user_type}', - '--tweakClass', 'net.minecraftforge.fml.common.launcher.FMLTweaker', - '--versionType', 'Forge' - ].join(' '), - libraries: [ - [ - // Package our universal jar as the 'main' jar Mojang's launcher loads. It will in turn load Forge's regular jars itself. - name: "${project.group}:${project.name}:${project.version}", - downloads: [ - artifact: [ - path: "${project.group.replace('.', '/')}/${project.name}/${project.version}/${project.name}-${project.version}.jar", - // Do not include the URL so that the installer/launcher won't grab it. This is also why we don't have the universal classifier - url: '', - sha1: Util.sha1(universalJar.archivePath), - size: universalJar.archivePath.length() - ] - ] - ] - ] - ] - Util.getArtifacts(project, project.configurations.installer, false) - .each { key, lib -> - json.libraries.add(lib) - } - Util.getLWJGLNatives(project.configurations.lwjglNatives, project.configurations.testCompile, lwjglLibraries[0], lwjglLibraries[1]) - .each { key, lib -> - json.libraries.add(lib) - } - output.text = new JsonBuilder(json).toPrettyString() - } - } - - tasks.register('installerJson') { - dependsOn launcherJson, genClientBinPatches/*, createClientSRG, createServerSRG*/ - ext { - output = file('build/install_profile.json') - installer_tools = "net.minecraftforge:installertools:$props.installer_tools_version" - } - inputs.file universalJar.archivePath - inputs.file genClientBinPatches.toolJar - inputs.file launcherJson.output - outputs.file output - doLast { - def libs = [ - "${project.group}:${project.name}:${project.version}": [ - name: "${project.group}:${project.name}:${project.version}", - downloads: [ - artifact: [ - path: "${project.group.replace('.', '/')}/${project.name}/${project.version}/${project.name}-${project.version}.jar", - // Do not include the URL so that the installer/launcher won't grab it. This is also why we don't have the universal classifier - url: '', - sha1: Util.sha1(universalJar.archivePath), - size: universalJar.archivePath.length() - ] - ] - ] - ] - def json = [ - _comment_: launcherJson.comment, - spec: 0, - profile: project.name, - version: launcherJson.id, - icon: 'data:image/png;base64,' + new String(Base64.getEncoder().encode(Files.readAllBytes(rootProject.file('icon.ico').toPath()))), - json: '/version.json', - path: "${project.group}:${project.name}:${project.version}", - logo: '/big_logo.png', - minecraft: props.minecraft_version, - welcome: "Welcome to the simple ${project.name.capitalize()} installer.", - data: [ ] as Map, - processors: [ ] - ] - Util.getClasspath(project, libs, project(':mcp').mcp.config.get().descriptor) // Tell it to download mcp_config - json.libraries = libs.values().sort { a, b -> a.name.compareTo(b.name) } - - output.text = new JsonBuilder(json).toPrettyString() - } - } - - tasks.register('extractObf2Srg', ExtractMCPData) { - dependsOn ':mcp:downloadConfig' - config = project(':mcp').downloadConfig.output - } - - tasks.register('deobfDataLzma') { - dependsOn extractObf2Srg - ext { - output_srg = file('build/deobfDataLzma/data.srg') - output = file('build/deobfDataLzma/data.lzma') - } - - doLast { - IMappingFile.load(extractObf2Srg.output.get().getAsFile()).write(output_srg.toPath(), IMappingFile.Format.SRG, false) - output_srg.withInputStream { ins -> - output.withOutputStream { outs -> - def lz = new LZMACompressorOutputStream(outs) - - def i = -1 - def buf = new byte[0x100] - while ((i = ins.read(buf)) != -1) - lz.write(buf, 0, i) - - lz.close() - } - } - } - } - - universalJar { - from(extra_files) - dependsOn(extractObf2Srg) - from(extractObf2Srg.output) { - rename { - "deobf_data-${props.minecraft_version}.tsrg" // TODO: Spacing? - } - } - dependsOn(genRuntimeBinPatches) - from(genRuntimeBinPatches.output) { - rename { - 'binpatches.pack.lzma' - } - } - doFirst { - def classpath = new StringBuilder() - def artifacts = Util.getArtifacts(project, project.configurations.installer, false) - artifacts.each { key, lib -> - classpath.append("libraries/${lib.downloads.artifact.path} ") - } - classpath += "minecraft_server.${props.minecraft_version}.jar" - manifest.attributes([ - 'Timestamp' : new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), - 'Main-Class' : 'net.minecraftforge.fml.relauncher.ServerLaunchWrapper', - 'Class-Path' : classpath.toString(), - 'Tweak-Class': 'net.minecraftforge.fml.common.launcher.FMLTweaker' - ]) - manifest.attributes([ - 'Specification-Title' : props.title, - 'Specification-Vendor' : props.vendor, - 'Specification-Version' : spec_version, - 'Implementation-Title' : project.group, - 'Implementation-Version': props.last_forge_version, - 'Implementation-Vendor' : props.vendor - ], 'net/minecraftforge/common/') - } - - archiveClassifier = 'universal' - } - - tasks.register('downloadInstaller', DownloadMavenArtifact) { - artifact = 'net.minecraftforge:installer:2.2.+:fatjar' - changing = true - } - - tasks.register('installerJar', Zip) { - dependsOn downloadInstaller, installerJson, launcherJson, genClientBinPatches, genServerBinPatches/*, 'signUniversalJar'*/ - archiveClassifier = 'installer' - archiveExtension = 'jar' //Needs to be Zip task to not override Manifest, so set extension - destinationDirectory = file('build/libs') - from(extra_files) - from(rootProject.file('/src/main/resources/forge_logo.png')) { - rename { 'big_logo.png' } - } - from(rootProject.file('/src/main/resources/url.png')) - from(universalJar) { - into("/maven/${project.group.replace('.', '/')}/${project.name}/${project.version}/") - rename { "${project.name }-${project.version }.jar" } - } - from(installerJson.output) - from(launcherJson.output) - from(zipTree(downloadInstaller.output)) { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } - } - - // TODO: Signing - /* - [universalJar, installerJar].each { t -> - task "sign${t.name.capitalize()}"(type: SignJar, dependsOn: t) { - onlyIf { - JAR_SIGNER != null && t.state.failure == null - } - def jarsigner = JAR_SIGNER == null ? [: ] : JAR_SIGNER - alias = 'forge' - storePass = jarsigner.storepass - keyPass = jarsigner.keypass - keyStore = jarsigner.keystore - inputFile = t.archivePath - outputFile = t.archivePath - doFirst { - project.logger.lifecycle('Signing: ' + inputFile) - } - } - t.finalizedBy(tasks.getByName("sign${t.name.capitalize()}")) - } - */ - - // TODO: MDK Task - - userdevConfig { - def artifacts = Util.getArtifacts(project, project.configurations.installer, true) - artifacts.each { key, lib -> - libraries.add(lib.name) - } - libraries.add('net.minecraftforge:legacydev:0.2.3.+:fatjar') - universalFilters.add('^(?!binpatches\\.pack\\.lzma$).*$') - - runs { - client { - main 'com.cleanroommc.boot.MainClient' - - environment 'tweakClass', 'net.minecraftforge.fml.common.launcher.FMLTweaker' - environment 'mainClass', 'top.outlands.foundation.boot.Foundation' - environment 'assetIndex', '{asset_index}' - environment 'assetDirectory', '{assets_root}' - environment 'nativesDirectory', '{natives}' - environment 'MC_VERSION', props.minecraft_version - environment 'MCP_MAPPINGS', '{mcp_mappings}' - environment 'MCP_TO_SRG', '{mcp_to_srg}' - environment 'FORGE_GROUP', project.group - environment 'FORGE_VERSION', props.last_forge_version - } - - server { - main 'com.cleanroommc.boot.MainServer' - - environment 'tweakClass', 'net.minecraftforge.fml.common.launcher.FMLServerTweaker' - environment 'mainClass', 'top.outlands.foundation.boot.Foundation' - environment 'MC_VERSION', props.minecraft_version - environment 'MCP_MAPPINGS', '{mcp_mappings}' - environment 'MCP_TO_SRG', '{mcp_to_srg}' - environment 'FORGE_GROUP', project.group - environment 'FORGE_VERSION', props.last_forge_version - } - } - } - - tasks.register('userdevExtras', Jar) { - dependsOn classes - from sourceSets.userdev.output - archiveClassifier = 'userdev-temp' - } - - tasks.register('userdevExtrasReobf', ReobfuscateJar) { - dependsOn userdevExtras, createMcp2Srg - input = tasks.userdevExtras.archiveFile - srg = tasks.createMcp2Srg.output - } - - userdevJar { - dependsOn userdevExtrasReobf - from(zipTree(tasks.userdevExtrasReobf.output)) { - into '/inject/' - } - from(sourceSets.userdev.output.resourcesDir) { - into '/inject/' - } - archiveClassifier = 'userdev' // Should be 'userdev' but FG5 hardcoded pre1.13 versions to userdev3 - } - - if (project.hasProperty('UPDATE_MAPPINGS')) { - extractRangeMap { - sources sourceSets.test.java.srcDirs - } - applyRangeMap { - sources sourceSets.test.java.srcDirs - } - sourceSets.test.java.srcDirs.each { extractMappedNew.addTarget it } - } - - - publishing { - repositories { - maven { - name = "outland" - url = "https://maven.outlands.top/releases" - credentials(PasswordCredentials) - authentication { - basic(BasicAuthentication) - } - } - } - publications { - maven(MavenPublication) { - groupId = "com.cleanroommc" - artifactId = "cleanroom" - version = rootProject.version - from components.java - artifact userdevJar - artifact universalJar - artifact sourcesJar - artifact installerJar - } - } - } - - test { - jvmArgs jvm_arguments + compiler_jvm_arguments - } - - artifacts { - archives universalJar - archives installerJar - } - -} - - tasks.withType(JavaCompile).configureEach { options.compilerArgs = options.compilerArgs + ProjectConstants.COMPILER_JVM_ARGUMENTS options.encoding = 'UTF-8' From ec3880ec50e070e46b1cd0cd0d888f3c7dceb49a Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 20 Dec 2025 11:14:56 +0800 Subject: [PATCH 69/91] Update build.gradle --- projects/cleanroom/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/cleanroom/build.gradle b/projects/cleanroom/build.gradle index f580804a6..af646c663 100644 --- a/projects/cleanroom/build.gradle +++ b/projects/cleanroom/build.gradle @@ -252,6 +252,7 @@ configurations { dependencies { compileOnly "com.cleanroommc:lwjglx:1.0.0" + compileOnly "org.jetbrains:annotations:26.0.2-1" installer "com.cleanroommc:lwjglxx:1.1.17" lwjglLibraries[0].each { installer "org.lwjgl:$it:$props.lwjgl_version" From 38a86d7d1fe98a09e3237bfa4949866038370099 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 20 Dec 2025 12:19:29 +0800 Subject: [PATCH 70/91] Add update icon for in game menu --- .../client/gui/GuiIngameMenu.java.patch | 32 ++++++++++++++++--- .../client/gui/GuiMainMenu.java.patch | 4 +-- .../gui/NotificationModUpdateScreen.java | 4 +-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.java.patch index 61f85c9dc..142b1a27c 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.java.patch @@ -1,18 +1,33 @@ --- before/net/minecraft/client/gui/GuiIngameMenu.java +++ after/net/minecraft/client/gui/GuiIngameMenu.java -@@ -30,9 +30,8 @@ +@@ -13,6 +13,7 @@ + { + private int saveStep; + private int visibleTime; ++ private net.minecraftforge.client.gui.NotificationModUpdateScreen modUpdateNotification; + + @Override + public void initGui() +@@ -30,13 +31,13 @@ this.buttonList.add(new GuiButton(4, this.width / 2 - 100, this.height / 4 + 24 + -16, I18n.format("menu.returnToGame"))); this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height / 4 + 96 + -16, 98, 20, I18n.format("menu.options"))); - GuiButton guibutton = this.addButton( - new GuiButton(7, this.width / 2 + 2, this.height / 4 + 96 + -16, 98, 20, I18n.format("menu.shareToLan")) - ); -+ this.buttonList.add(new GuiButton(12, this.width / 2 + 2, this.height / 4 + 96 + i, 98, 20, I18n.format("fml.menu.modoptions"))); ++ GuiButton modButton = this.addButton(new GuiButton(12, this.width / 2 + 2, this.height / 4 + 96 + i, 98, 20, I18n.format("fml.menu.mods"))); + GuiButton guibutton = this.addButton(new GuiButton(7, this.width / 2 - 100, this.height / 4 + 72 + -16, 200, 20, I18n.format("menu.shareToLan", new Object[0]))); guibutton.enabled = this.mc.isSingleplayer() && !this.mc.getIntegratedServer().getPublic(); - this.buttonList - .add(new GuiButton(5, this.width / 2 - 100, this.height / 4 + 48 + -16, 98, 20, I18n.format("gui.advancements"))); -@@ -77,13 +76,19 @@ +- this.buttonList +- .add(new GuiButton(5, this.width / 2 - 100, this.height / 4 + 48 + -16, 98, 20, I18n.format("gui.advancements"))); ++ this.buttonList.add(new GuiButton(5, this.width / 2 - 100, this.height / 4 + 48 + -16, 98, 20, I18n.format("gui.advancements"))); + this.buttonList.add(new GuiButton(6, this.width / 2 + 2, this.height / 4 + 48 + -16, 98, 20, I18n.format("gui.stats"))); ++ ++ this.modUpdateNotification = net.minecraftforge.client.gui.NotificationModUpdateScreen.init(this, modButton); + } + + @Override +@@ -77,13 +78,19 @@ this.mc.setIngameFocus(); break; case 5: @@ -32,3 +47,10 @@ } } +@@ -100,5 +107,6 @@ + this.drawDefaultBackground(); + this.drawCenteredString(this.fontRenderer, I18n.format("menu.game"), this.width / 2, 40, 16777215); + super.drawScreen(mouseX, mouseY, partialTicks); ++ this.modUpdateNotification.drawScreen(mouseX, mouseY, partialTicks); + } + } diff --git a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch index c70385ce6..5ee6665f0 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/GuiMainMenu.java.patch @@ -48,7 +48,7 @@ - this.realmsNotification.setGuiSize(this.width, this.height); - this.realmsNotification.initGui(); - } -+ modUpdateNotification = net.minecraftforge.client.gui.NotificationModUpdateScreen.init(this, modButton); ++ this.modUpdateNotification = net.minecraftforge.client.gui.NotificationModUpdateScreen.init(this, this.modButton); } private void addSingleplayerMultiplayerButtons(int p_73969_1_, int p_73969_2_) @@ -56,7 +56,7 @@ this.buttonList.add(new GuiButton(1, this.width / 2 - 100, p_73969_1_, I18n.format("menu.singleplayer"))); this.buttonList.add(new GuiButton(2, this.width / 2 - 100, p_73969_1_ + p_73969_2_ * 1, I18n.format("menu.multiplayer"))); - this.realmsButton = this.addButton(new GuiButton(14, this.width / 2 - 100, p_73969_1_ + p_73969_2_ * 2, I18n.format("menu.online"))); -+ this.buttonList.add(modButton = new GuiButton(6, this.width / 2 - 100, p_73969_1_ + p_73969_2_ * 2, I18n.format("fml.menu.mods"))); ++ this.buttonList.add(this.modButton = new GuiButton(6, this.width / 2 - 100, p_73969_1_ + p_73969_2_ * 2, I18n.format("fml.menu.mods"))); } private void addDemoButtons(int p_73972_1_, int p_73972_2_) diff --git a/src/main/java/net/minecraftforge/client/gui/NotificationModUpdateScreen.java b/src/main/java/net/minecraftforge/client/gui/NotificationModUpdateScreen.java index 7f3cba538..ffbee19ee 100644 --- a/src/main/java/net/minecraftforge/client/gui/NotificationModUpdateScreen.java +++ b/src/main/java/net/minecraftforge/client/gui/NotificationModUpdateScreen.java @@ -91,10 +91,10 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) GlStateManager.popMatrix(); } - public static NotificationModUpdateScreen init(GuiMainMenu guiMainMenu, GuiButton modButton) + public static NotificationModUpdateScreen init(GuiScreen guiScreen, GuiButton modButton) { NotificationModUpdateScreen notificationModUpdateScreen = new NotificationModUpdateScreen(modButton); - notificationModUpdateScreen.setGuiSize(guiMainMenu.width, guiMainMenu.height); + notificationModUpdateScreen.setGuiSize(guiScreen.width, guiScreen.height); notificationModUpdateScreen.initGui(); return notificationModUpdateScreen; } From 1f644917b3c639b7d299f1bcfdc86d2d29b00747 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:43:53 +0800 Subject: [PATCH 71/91] Fix some items not high enough --- .../client/screen/CatalogueModListScreen.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index c39a3cfc9..907d800df 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -660,7 +660,17 @@ private void drawIcon(int top, int left) { GlStateManager.enableDepth(); GlStateManager.enableRescaleNormal(); RenderHelper.enableGUIStandardItemLighting(); + + float screenZ = CatalogueModListScreen.this.zLevel; + float itemRenderZ = CatalogueModListScreen.this.itemRender.zLevel; + CatalogueModListScreen.this.zLevel = 300.0F; + CatalogueModListScreen.this.itemRender.zLevel = 300.0F; + CatalogueModListScreen.this.itemRender.renderItemAndEffectIntoGUI(this.icon, left + 4, top + 2); + + CatalogueModListScreen.this.zLevel = screenZ; + CatalogueModListScreen.this.itemRender.zLevel = itemRenderZ; + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.disableDepth(); GlStateManager.disableRescaleNormal(); From 8eab7216e4d9b19f715493d77b52a87d0c4e1f61 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 21 Dec 2025 11:19:43 +0800 Subject: [PATCH 72/91] Fix window width and height --- .../catalogue/client/screen/widget/DropdownMenu.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java index 9c1fb6208..c69618e61 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java @@ -13,6 +13,7 @@ import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.init.SoundEvents; import net.minecraft.util.ResourceLocation; @@ -113,7 +114,8 @@ private void deepClose() { public void drawScreen(Minecraft minecraft, int mouseX, int mouseY, float deltaTick) { GlStateManager.pushMatrix(); - drawRect(0, 0, minecraft.displayWidth, minecraft.displayHeight, 0x50000000); + final ScaledResolution sr = new ScaledResolution(minecraft); + drawRect(0, 0, sr.getScaledWidth(), sr.getScaledHeight(), 0x50000000); drawRect(this.getX(), this.getY(), this.getX() + this.getWidth(), this.getY() + this.getHeight(), 0xAA000000); this.items.forEach(widget -> widget.drawWidget(minecraft, mouseX, mouseY, deltaTick)); if (this.subMenu != null) { From 2c7ced29fdfc45498f3a22f83199db4fecac6d8e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 28 Dec 2025 16:29:09 +0800 Subject: [PATCH 73/91] Rehandle child mods --- .../catalogue/CatalogueConfig.java | 1 - .../catalogue/client/CleanroomModData.java | 90 +++++++------ .../catalogue/client/IModData.java | 15 ++- .../client/screen/CatalogueModListScreen.java | 118 ++++++++++++------ .../client/screen/MinecraftModData.java | 21 +++- .../client/screen/widget/DropdownMenu.java | 4 +- .../platform/CleanroomPlatformHelper.java | 42 +++++-- .../assets/catalogue/lang/en_au.lang | 6 + .../assets/catalogue/lang/en_us.lang | 6 + .../assets/catalogue/lang/zh_cn.lang | 6 + 10 files changed, 213 insertions(+), 96 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index 364291700..400e741ea 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -23,7 +23,6 @@ public class CatalogueConfig { }) @Config.LangKey("catalogue.config.library_list") public static String[] libraryList = new String[]{ - "minecraft", "forge", "FML", "mcp", diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index f08fb9992..d5310ad6f 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -19,9 +19,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** @@ -32,14 +30,18 @@ public class CleanroomModData implements IModData { public static final List LIB_MODS = Arrays.asList(CatalogueConfig.libraryList); public static final List IGNORED_DEPENDENCIES = Arrays.asList(CatalogueConfig.ignoredDependenciesList); - private final ModContainer info; + private final @NotNull ModContainer info; + private final @Nullable ModMetadata metadata; private final Type type; private final Set dependencies; + private final Set childMods; - public CleanroomModData(ModContainer info) { + public CleanroomModData(@NotNull ModContainer info) { this.info = info; + this.metadata = info.getMetadata(); this.type = analyzeType(info); - this.dependencies = this.analyzeDependencies(info); + this.dependencies = analyzeDependencies(info); + this.childMods = analyzeChildMods(info); } @Override @@ -70,71 +72,73 @@ public String getInnerVersion() { @Nullable @Override public String getDescription() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.description : null; + return this.metadata != null ? this.metadata.description : null; } @Nullable @Override public String getItemIcon() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.iconItem : null; + return this.metadata != null ? this.metadata.iconItem : null; } @Nullable @Override public String getImageIcon() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.iconFile : null; + return this.metadata != null ? this.metadata.iconFile : null; } @Nullable @Override public String getLicense() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.license : null; + return this.metadata != null ? this.metadata.license : null; } @Nullable @Override public String getCredits() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.credits : null; + return this.metadata != null ? this.metadata.credits : null; } @Nullable @Override public String getAuthors() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.getAuthorList() : null; + return this.metadata != null ? this.metadata.getAuthorList() : null; } @Nullable @Override public String getHomepage() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.url : null; + return this.metadata != null ? this.metadata.url : null; } @Nullable @Override public String getIssueTracker() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.issueTrackerUrl : null; + return this.metadata != null ? this.metadata.issueTrackerUrl : null; } @Nullable @Override public String getBanner() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.logoFile : null; + return this.metadata != null ? this.metadata.logoFile : null; } @Nullable @Override public String getBackground() { - ModMetadata metadata = this.getMetadata(); - return metadata != null ? metadata.backgroundFile : null; + return this.metadata != null ? this.metadata.backgroundFile : null; + } + + @Nullable + @Override + public String getChildModNames() { + return this.metadata != null ? this.metadata.getChildModList() : null; + } + + @Nullable + @Override + public String getParentModName() { + return this.metadata != null && this.metadata.parentMod != null ? this.metadata.parentMod.getName() : null; } @Nullable @@ -147,24 +151,25 @@ public Update getUpdate() { return null; } + @NotNull @Override public Set getDependencies() { return this.dependencies; } + @NotNull + @Override + public Set getChildMods() { + return this.childMods; + } + @Override public boolean hasConfig() { - if (this.info == null) return false; IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(this.info); if (guiFactory == null) return false; return guiFactory.hasConfigGui(); } - @Override - public boolean isLibrary() { - return this.info.getModId().equals("forge") || this.type != Type.DEFAULT; - } - @Override public void openConfigScreen(Minecraft minecraft, GuiScreen parent) { try { @@ -216,25 +221,30 @@ public IResourcePack getResourcePack() { return FMLClientHandler.instance().getResourcePackFor(this.getModId()); } - @Nullable - private ModMetadata getMetadata() { - ModMetadata metadata = this.info.getMetadata(); - return metadata != null && !metadata.autogenerated ? metadata : null; - } - private Type analyzeType(@NotNull ModContainer info) { - if (LIB_MODS.contains(info.getModId())) { + if (this.metadata != null && this.metadata.parentMod != null) { + return Type.CHILD; + } else if (LIB_MODS.contains(info.getModId())) { return Type.LIBRARY; } else { return Type.DEFAULT; } } - private Set analyzeDependencies(@NotNull ModContainer source) { + private static @NotNull Set analyzeDependencies(@NotNull ModContainer source) { List versions = source.getDependencies(); return versions.stream() .map(ArtifactVersion::getLabel) .filter(modid -> !IGNORED_DEPENDENCIES.contains(modid)) .collect(Collectors.toUnmodifiableSet()); } + + private static @NotNull Set analyzeChildMods(@NotNull ModContainer source) { + ModMetadata metadata = source.getMetadata(); + if (metadata == null) return Collections.emptySet(); + return metadata.childMods.stream() + .filter(Objects::nonNull) + .map(ModContainer::getModId) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/IModData.java b/src/main/java/com/cleanroommc/catalogue/client/IModData.java index 737e80f13..02af3a310 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/IModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/IModData.java @@ -5,6 +5,7 @@ import net.minecraft.client.resources.IResourcePack; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.TextFormatting; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Set; @@ -53,17 +54,25 @@ public interface IModData { @Nullable String getBackground(); + @Nullable + String getChildModNames(); + + @Nullable + String getParentModName(); + @Nullable Update getUpdate(); @Nullable IResourcePack getResourcePack(); + @NotNull Set getDependencies(); //TODO lazily - boolean hasConfig(); + @NotNull + Set getChildMods(); - boolean isLibrary(); + boolean hasConfig(); void openConfigScreen(Minecraft minecraft, GuiScreen parent); @@ -79,7 +88,7 @@ record Update(boolean animated, String url, int texOffset, ResourceLocation text enum Type { DEFAULT(TextFormatting.RESET), LIBRARY(TextFormatting.DARK_GRAY), - GENERATED(TextFormatting.AQUA); + CHILD(TextFormatting.AQUA); private final TextFormatting style; diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 907d800df..5fb7bb83d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -59,6 +59,7 @@ public class CatalogueModListScreen extends GuiScreen implements DropdownMenuHan private static final Comparator SORT_FAVOURITES_FIRST = Comparator.comparing(ModListEntry::getData, Comparator.comparing(data -> FAVOURITES.has(data.getModId()))).reversed().thenComparing(SORT_ALPHABETICALLY); private static final MutableObject OPTION_QUERY = new MutableObject<>(""); private static final MutableBoolean OPTION_HIDE_LIBRARIES = new MutableBoolean(true); + private static final MutableBoolean OPTION_HIDE_CHILD_MODS = new MutableBoolean(true); private static final MutableBoolean OPTION_CONFIGS_ONLY = new MutableBoolean(false); private static final MutableBoolean OPTION_UPDATES_ONLY = new MutableBoolean(false); private static final MutableBoolean OPTION_FAVOURITES_ONLY = new MutableBoolean(false); @@ -75,7 +76,10 @@ public class CatalogueModListScreen extends GuiScreen implements DropdownMenuHan private static final Pattern MOD_ID_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{1,63}$"); private static final Supplier> COUNTS = Suppliers.memoize(() -> { int[] counts = new int[2]; - CACHED_MODS.forEach((modId, data) -> counts[data.isLibrary() ? 1 : 0]++); + CACHED_MODS.forEach((modId, data) -> { + if (data.getType() == IModData.Type.CHILD) return; + counts[data.getType() == IModData.Type.LIBRARY ? 1 : 0]++; + }); return Pair.of(counts[0], counts[1]); }); private static final Map SEARCH_FILTERS = ImmutableMap.builder() @@ -84,7 +88,14 @@ public class CatalogueModListScreen extends GuiScreen implements DropdownMenuHan return target != null && target.getDependencies().contains(data.getModId()); })) .put("dependents", new SearchFilter((query, data) -> { - return data.getDependencies().contains(query.toLowerCase(Locale.ENGLISH)); + return data.getDependencies().stream().anyMatch(query::equalsIgnoreCase); + })) + .put("childmods", new SearchFilter((query, data) -> { + IModData target = CACHED_MODS.get(query.toLowerCase(Locale.ENGLISH)); + return target != null && target.getChildMods().contains(data.getModId()); + })) + .put("parentmod", new SearchFilter((query, data) -> { + return data.getChildMods().stream().anyMatch(query::equalsIgnoreCase); })).build(); private static final TextFormatting SEARCH_FILTER_KEY = TextFormatting.GOLD; private static final TextFormatting SEARCH_FILTER_VALUE = TextFormatting.WHITE; @@ -115,7 +126,7 @@ public CatalogueModListScreen(GuiScreen parent) { super(); this.parentScreen = parent; if (!loaded) { - ClientServices.PLATFORM.getAllModData().forEach(data -> CACHED_MODS.put(data.getModId(), data)); + ClientServices.PLATFORM.getAllModData().forEach(data -> CACHED_MODS.put(data.getModId().toLowerCase(Locale.ENGLISH), data)); CACHED_MODS.put("minecraft", new MinecraftModData()); // Override minecraft BANNER_CACHE.put("minecraft", new ImageInfo(MINECRAFT_LOGO, 1024, 256, () -> { })); @@ -147,9 +158,9 @@ public int getWidth() { }; this.searchTextField.setFormatter(this::formatQuery); this.searchTextField.setMaxStringLength(128); - this.searchTextField.setText(OPTION_QUERY.getValue()); + this.searchTextField.setText(OPTION_QUERY.get()); this.searchTextField.setResponder(s -> { - if (!OPTION_QUERY.getValue().equals(s)) { + if (!OPTION_QUERY.get().equals(s)) { OPTION_QUERY.setValue(s); this.updateSearchFieldSuggestion(s); this.modList.filterAndUpdateList(); @@ -260,6 +271,10 @@ public void actionPerformed(@NotNull GuiButton button) { .addCheckbox(I18n.format("catalogue.gui.hide_libraries"), OPTION_HIDE_LIBRARIES, newValue -> { this.modList.filterAndUpdateList(); return false; + }) + .addCheckbox(I18n.format("catalogue.gui.hide_child_mods"), OPTION_HIDE_CHILD_MODS, newValue -> { + this.modList.filterAndUpdateList(); + return false; }).build(); menu.toggle(button); break; @@ -280,7 +295,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawModInfo(disableableMouseX, disableableMouseY, partialTicks); super.drawScreen(disableableMouseX, disableableMouseY, partialTicks); - if (OPTION_QUERY.getValue().startsWith("@")) { + if (OPTION_QUERY.get().startsWith("@")) { int iconX = this.searchTextField.x + this.searchTextField.width - 15; int iconY = this.searchTextField.y + (this.searchTextField.height - 10) / 2; this.mc.getTextureManager().bindTexture(CatalogueIconButton.TEXTURE); @@ -291,7 +306,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { } } - Optional optional = Optional.ofNullable(CACHED_MODS.get(CatalogueConstants.MOD_ID)); + Optional optional = Optional.ofNullable(CACHED_MODS.get(CatalogueConstants.MOD_ID.toLowerCase(Locale.ENGLISH))); optional.ifPresent(this::loadAndCacheLogo); ImageInfo bannerInfo = BANNER_CACHE.get(CatalogueConstants.MOD_ID); if (bannerInfo != null) { @@ -433,7 +448,10 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { this.searchTextField.drawTextBox(); String modsLabel = TextFormatting.BOLD + I18n.format("catalogue.gui.mod_list"); - String countLabel = TextFormatting.GRAY + "(" + CACHED_MODS.size() + ")"; + Pair counts = COUNTS.get(); + int modCount = counts.getLeft(); + int libCount = counts.getRight(); + String countLabel = TextFormatting.GRAY + "(" + (modCount + libCount) + ")"; String title = modsLabel + " " + countLabel; int titleWidth = this.fontRenderer.getStringWidth(title); int titleLeft = this.modList.left + (this.modList.width - titleWidth) / 2; @@ -441,10 +459,9 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { int countLabelWidth = this.fontRenderer.getStringWidth(countLabel); if (ClientHelper.isMouseWithin(titleLeft + titleWidth - countLabelWidth, 10, countLabelWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { - Pair counts = COUNTS.get(); - List lines = List.of( - I18n.format("catalogue.gui.mod_count", counts.getLeft()), - I18n.format("catalogue.gui.library_count", counts.getRight()) + List lines = Arrays.asList( + I18n.format("catalogue.gui.mod_count", modCount), + I18n.format("catalogue.gui.library_count", libCount) ); this.setActiveTooltip(lines); this.tooltipYOffset = 10; @@ -453,7 +470,7 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { private class ModList extends CatalogueListSelection { private static final Predicate SEARCH_PREDICATE = data -> { - String query = OPTION_QUERY.getValue(); + String query = OPTION_QUERY.get(); if (query.startsWith("@")) { return performSearchFilter(query, data); } @@ -463,7 +480,7 @@ private class ModList extends CatalogueListSelection { }; private static final Predicate FILTER_PREDICATE = data -> { // We ignore filters when using special query - String query = OPTION_QUERY.getValue(); + String query = OPTION_QUERY.get(); if (query.startsWith("@")) { return true; } @@ -473,7 +490,10 @@ private class ModList extends CatalogueListSelection { if (OPTION_UPDATES_ONLY.booleanValue() && (data.getUpdate() == null || !data.getUpdate().updatable())) { return false; } - if (OPTION_HIDE_LIBRARIES.booleanValue() && data.isLibrary()) { + if (OPTION_HIDE_LIBRARIES.booleanValue() && data.getType() == IModData.Type.LIBRARY) { + return false; + } + if (OPTION_HIDE_CHILD_MODS.booleanValue() && data.getType() == IModData.Type.CHILD) { return false; } //noinspection RedundantIfStatement @@ -504,7 +524,7 @@ public void filterAndUpdateList() { .filter(SEARCH_PREDICATE) .filter(FILTER_PREDICATE) .map(data -> new ModListEntry(data, this)) - .sorted(OPTION_SORT.getValue()) + .sorted(OPTION_SORT.get()) .collect(Collectors.toList()); this.replaceEntries(entries); if (CatalogueModListScreen.this.selectedModData != null) { @@ -570,7 +590,7 @@ private static boolean performSearchFilter(@NotNull String query, IModData data) } private String formatQuery(String partial, int displayPos) { - String query = OPTION_QUERY.getValue(); + String query = OPTION_QUERY.get(); if (!query.startsWith("@")) { return partial; } @@ -607,7 +627,7 @@ private class ModListEntry implements CatalogueListExtended.IListEntry { public ModListEntry(@NotNull IModData data, @NotNull ModList list) { this.data = data; this.list = list; - this.button = new PinnedButton(data.getModId()); + this.button = new PinnedButton(); this.icon = this.getItemIcon(); } @@ -616,7 +636,7 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, this.hovered = hovered; // Draws mod name and version boolean inOptionsMenu = CatalogueModListScreen.this.menu != null; - boolean drawFavouriteIcon = !inOptionsMenu && !this.list.shouldHideFavourites() && ClientHelper.isMouseWithin(left + rowWidth - rowHeight - 4, top, rowHeight + 4, rowHeight, mouseX, mouseY) || FAVOURITES.has(this.data.getModId()); + boolean drawFavouriteIcon = !inOptionsMenu && this.data.getType() != IModData.Type.CHILD && !this.list.shouldHideFavourites() && ClientHelper.isMouseWithin(left + rowWidth - rowHeight - 4, top, rowHeight + 4, rowHeight, mouseX, mouseY) || FAVOURITES.has(this.data.getModId()); drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModName(drawFavouriteIcon), left + 24, top + 2, 0xFFFFFF); drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModVersion(drawFavouriteIcon), left + 24, top + 12, 0xFFFFFF); @@ -738,7 +758,7 @@ private void drawIcon(int top, int left) { .findFirst(); // If the mod doesn't specify an item to use, Catalogue will attempt to get an item from the mod - if (!optional.isPresent()) { + if (optional.isEmpty()) { optional = ForgeRegistries.ITEMS.getValuesCollection().stream() .filter(Objects::nonNull) .filter(item -> { @@ -763,10 +783,7 @@ private void drawIcon(int top, int left) { private String getFormattedModName(boolean favouriteIconVisible) { String name = this.data.getDisplayName(); name = this.getFormattedText(name, favouriteIconVisible); - if (this.data.isLibrary()) { - return TextFormatting.DARK_GRAY + name; - } - return name; + return data.getType().getStyle() + name; } @NotNull @@ -798,7 +815,7 @@ public IModData getData() { @Override public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseButton, int relativeX, int relativeY) { if (mouseButton == 1) { - DropdownMenu menu = DropdownMenu.builder(CatalogueModListScreen.this) + DropdownMenu.Builder builder = DropdownMenu.builder(CatalogueModListScreen.this) .setMinItemSize(0, 16) .setAlignment(DropdownMenu.Alignment.BELOW_LEFT) .addItem(I18n.format("catalogue.gui.show_dependencies"), () -> { @@ -808,11 +825,28 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseButt .addItem(I18n.format("catalogue.gui.show_dependents"), () -> { String filter = "@dependents:" + this.data.getModId(); CatalogueModListScreen.this.searchTextField.setText(filter); - }).build(); + }); + if (this.data.getType() == IModData.Type.CHILD) { + builder.addItem(I18n.format("catalogue.gui.show_parent_mod"), () -> { + String filter = "@parentmod:" + this.data.getModId(); + CatalogueModListScreen.this.searchTextField.setText(filter); + }); + } else if (!this.data.getChildMods().isEmpty()) { + builder.addItem(I18n.format("catalogue.gui.show_child_mods"), () -> { + String filter = "@childmods:" + this.data.getModId(); + CatalogueModListScreen.this.searchTextField.setText(filter); + }); + } + DropdownMenu menu = builder.build(); menu.toggle(mouseX, mouseY); return true; } else if (mouseButton == 0) { - if (this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) return true; + if (this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) { + FAVOURITES.toggle(this.data.getModId()); + ModListEntry.this.list.filterAndUpdateList(); + this.button.playPressSound(mc.getSoundHandler()); + return true; + } CatalogueModListScreen.this.setSelectedModData(this.data); this.list.setSelected(this); return true; @@ -827,11 +861,8 @@ public boolean isMouseOver() { private class PinnedButton extends GuiButton { private static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/icons.png"); - private final String modId; - - public PinnedButton(String modId) { + public PinnedButton() { super(0, 0, 0, 10, 10, ""); - this.modId = modId; } @Override @@ -839,7 +870,7 @@ public void drawButton(@NotNull Minecraft mc, int mouseX, int mouseY, float part if (!this.visible) return; this.hovered = ModListEntry.this.isMouseOver() && ClientHelper.isMouseWithin(this.x, this.y, this.width, this.height, mouseX, mouseY); this.mouseDragged(mc, mouseX, mouseY); - int textureU = FAVOURITES.has(this.modId) ? 10 : 0; + int textureU = FAVOURITES.has(ModListEntry.this.data.getModId()) ? 10 : 0; GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); mc.getTextureManager().bindTexture(TEXTURE); @@ -849,13 +880,7 @@ public void drawButton(@NotNull Minecraft mc, int mouseX, int mouseY, float part @Override public boolean mousePressed(@NotNull Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY) && !ModListEntry.this.list.shouldHideFavourites()) { - FAVOURITES.toggle(this.modId); - ModListEntry.this.list.filterAndUpdateList(); - this.playPressSound(mc.getSoundHandler()); - return true; - } - return false; + return super.mousePressed(mc, mouseX, mouseY) && ModListEntry.this.data.getType() != IModData.Type.CHILD && !ModListEntry.this.list.shouldHideFavourites(); } } } @@ -920,6 +945,19 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { int labelOffset = this.height - 18; + // Draw child mods + String childMods = this.selectedModData.getChildModNames(); + if (childMods != null && !childMods.isBlank()) { + this.drawStringWithLabel("catalogue.gui.child_mods", childMods, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + labelOffset -= 15; + } + + String parentMod = this.selectedModData.getParentModName(); + if (parentMod != null && !parentMod.isBlank()) { + this.drawStringWithLabel("catalogue.gui.parent_mod", parentMod, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + labelOffset -= 15; + } + // Draw license String license = this.selectedModData.getLicense(); if (license != null && !license.isBlank()) { @@ -1227,6 +1265,8 @@ private void setSelectedModData(IModData data) { */ private int getFooterTextElementCount(@NotNull IModData data) { int count = 0; + if (data.getChildModNames() != null && !data.getChildModNames().isBlank()) count++; + if (data.getParentModName() != null && !data.getParentModName().isBlank()) count++; if (data.getLicense() != null && !data.getLicense().isBlank()) count++; if (data.getCredits() != null && !data.getCredits().isBlank()) count++; if (data.getAuthors() != null && !data.getAuthors().isBlank()) count++; diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java index c9fa61f8e..f0cc524be 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java @@ -5,6 +5,7 @@ import net.minecraft.client.gui.GuiOptions; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.resources.IResourcePack; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; @@ -100,6 +101,18 @@ public String getBackground() { return null; } + @Nullable + @Override + public String getChildModNames() { + return null; + } + + @Nullable + @Override + public String getParentModName() { + return null; + } + @Nullable @Override public Update getUpdate() { @@ -112,18 +125,20 @@ public IResourcePack getResourcePack() { return null; } + @NotNull @Override public Set getDependencies() { return Collections.emptySet(); } + @NotNull @Override - public boolean hasConfig() { - return true; + public Set getChildMods() { + return Collections.emptySet(); } @Override - public boolean isLibrary() { + public boolean hasConfig() { return true; } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java index c69618e61..9418b5e39 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java @@ -300,12 +300,12 @@ public void drawWidget(Minecraft minecraft, int mouseX, int mouseY, float deltaT GlStateManager.enableBlend(); GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - drawModalRectWithCustomSizedTexture(this.getX() + this.getWidth() - 14 - offset, this.getY() + offset, this.hovered ? 14 : 0, this.holder.getValue() ? 14 : 0, 14, 14, 64, 64); + drawModalRectWithCustomSizedTexture(this.getX() + this.getWidth() - 14 - offset, this.getY() + offset, this.hovered ? 14 : 0, this.holder.get() ? 14 : 0, 14, 14, 64, 64); } @Override public void onClick(int mouseX, int mouseY) { - boolean newValue = !this.holder.getValue(); + boolean newValue = !this.holder.get(); this.holder.setValue(newValue); if (this.callback.apply(newValue)) { this.parent.deepClose(); diff --git a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java index d95d27986..c3b9d07ba 100644 --- a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java @@ -5,6 +5,7 @@ import com.cleanroommc.catalogue.client.CleanroomModData; import com.cleanroommc.catalogue.client.IModData; import com.cleanroommc.catalogue.platform.services.IPlatformHelper; +import com.google.common.base.Strings; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiVideoSettings; @@ -12,6 +13,8 @@ import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.ModMetadata; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.image.BufferedImage; @@ -21,6 +24,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * Author: MrCrayfish @@ -29,20 +33,35 @@ public class CleanroomPlatformHelper implements IPlatformHelper { @Override public List getAllModData() { - List dataList = new ArrayList<>(); - // Handle special containers + List containerList = Loader.instance().getActiveModList(); + + // Add special mod containers ArrayList specialContainerList = new ArrayList<>(); FMLClientHandler.instance().addSpecialModEntries(specialContainerList); - specialContainerList.removeIf(modContainer -> { + containerList.addAll(specialContainerList); + + // Add child mods to metadata + for (ModContainer container : containerList) { + if (container == null) continue; + ModMetadata metadata = container.getMetadata(); + if (metadata != null && metadata.parentMod == null && !Strings.isNullOrEmpty(metadata.parent)) { + ModContainer parentContainer = Loader.instance().getIndexedModList().get(metadata.parent); + if (parentContainer != null) { + metadata.parentMod = parentContainer; + parentContainer.getMetadata().childMods.add(container); + } + } + } + + List dataList = new ArrayList<>(); + containerList.removeIf(modContainer -> { if (modContainer.getModId().equals("optifine")) { dataList.add(new OFData(modContainer)); return true; } return false; }); - List containerList = Loader.instance().getActiveModList(); - containerList.addAll(specialContainerList); - dataList.addAll(containerList.stream().map(CleanroomModData::new).toList()); + dataList.addAll(containerList.stream().map(CleanroomModData::new).collect(Collectors.toList())); return dataList; } @@ -88,16 +107,23 @@ public int getIconLimit() { } private static class OFData extends CleanroomModData { + private final String version; + private OFData(ModContainer info) { super(info); + this.version = this.getDisplayVersion(); } - @Override - public String getVersion() { + private @NotNull String getDisplayVersion() { String version = this.getInnerVersion(); return version.substring(version.indexOf("OptiFine_1.12.2_") + 16); } + @Override + public String getVersion() { + return this.version; + } + @Nullable @Override public String getDescription() { diff --git a/src/main/resources/assets/catalogue/lang/en_au.lang b/src/main/resources/assets/catalogue/lang/en_au.lang index 47e375a78..5f9fb879d 100644 --- a/src/main/resources/assets/catalogue/lang/en_au.lang +++ b/src/main/resources/assets/catalogue/lang/en_au.lang @@ -3,6 +3,8 @@ catalogue.gui.mod_list=Mods catalogue.gui.search=Search catalogue.gui.config=Config catalogue.gui.open_mods_folder=Open Mods Folder +catalogue.gui.child_mods=Child Mod(s): %s +catalogue.gui.parent_mod=Parent Mod: %s catalogue.gui.licenses=License(s): %s catalogue.gui.authors=Author(s): %s catalogue.gui.contributors=Contributor(s): %s @@ -28,12 +30,16 @@ catalogue.gui.sort.alphabetically=A to Z catalogue.gui.sort.alphabetically_reverse=Z to A catalogue.gui.sort.favourites_first=Favourites First catalogue.gui.hide_libraries=Hide Libraries +catalogue.gui.hide_child_mods=Hide Child Mods catalogue.gui.no_mods=No mods... catalogue.gui.mod_count=%s Mods catalogue.gui.library_count=%s Libraries +catalogue.gui.child_mod_count=%s Child Mods catalogue.gui.advanced_search.info=Advanced search queries will ignore any active filters catalogue.gui.show_dependencies=Show Dependencies catalogue.gui.show_dependents=Show Dependents +catalogue.gui.show_child_mods=Show Child Mods +catalogue.gui.show_parent_mod=Show Parent Mod catalogue.gui.website=Website catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index 001cf8dc0..b0d0579bc 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -3,6 +3,8 @@ catalogue.gui.mod_list=Mods catalogue.gui.search=Search catalogue.gui.config=Config catalogue.gui.open_mods_folder=Open Mods Folder +catalogue.gui.child_mods=Child Mod(s): %s +catalogue.gui.parent_mod=Parent Mod: %s catalogue.gui.licenses=License(s): %s catalogue.gui.authors=Author(s): %s catalogue.gui.contributors=Contributor(s): %s @@ -28,12 +30,16 @@ catalogue.gui.sort.alphabetically=A to Z catalogue.gui.sort.alphabetically_reverse=Z to A catalogue.gui.sort.favourites_first=Favorites First catalogue.gui.hide_libraries=Hide Libraries +catalogue.gui.hide_child_mods=Hide Child Mods catalogue.gui.no_mods=No mods... catalogue.gui.mod_count=%s Mods catalogue.gui.library_count=%s Libraries +catalogue.gui.child_mod_count=%s Child Mods catalogue.gui.advanced_search.info=Advanced search queries will ignore any active filters catalogue.gui.show_dependencies=Show Dependencies catalogue.gui.show_dependents=Show Dependents +catalogue.gui.show_child_mods=Show Child Mods +catalogue.gui.show_parent_mod=Show Parent Mod catalogue.gui.website=Website catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index 5ccc08980..d9609612d 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -3,6 +3,8 @@ catalogue.gui.mod_list=模组 catalogue.gui.search=搜索 catalogue.gui.config=配置 catalogue.gui.open_mods_folder=打开模组文件夹 +catalogue.gui.child_mods=子模组:%s +catalogue.gui.parent_mod=父模组:%s catalogue.gui.licenses=许可协议:%s catalogue.gui.authors=作者:%s catalogue.gui.contributors=贡献者:%s @@ -28,12 +30,16 @@ catalogue.gui.sort.alphabetically=A到Z catalogue.gui.sort.alphabetically_reverse=Z到A catalogue.gui.sort.favourites_first=收藏优先 catalogue.gui.hide_libraries=隐藏库模组 +catalogue.gui.hide_child_mods=隐藏子模组 catalogue.gui.no_mods=没有模组…… catalogue.gui.mod_count=%s个模组 catalogue.gui.library_count=%s个库 +catalogue.gui.child_mod_count=%s个子模组 catalogue.gui.advanced_search.info=高级搜索查询将忽略任何激活的筛选器 catalogue.gui.show_dependencies=显示前置模组 catalogue.gui.show_dependents=显示依赖模组 +catalogue.gui.show_child_mods=显示子模组 +catalogue.gui.show_parent_mod=显示父模组 catalogue.gui.website=网站 catalogue.gui.submit_bug=提交Bug catalogue.gui.mod_id=Mod ID:%s From cfbca69df66bc10e0a97a1beafe2c0b811d07be8 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 30 Jan 2026 19:10:33 +0800 Subject: [PATCH 74/91] Inner version fallback --- .../catalogue/client/CleanroomModData.java | 2 +- .../client/screen/CatalogueModListScreen.java | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index d5310ad6f..8978e855d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -245,6 +245,6 @@ private Type analyzeType(@NotNull ModContainer info) { return metadata.childMods.stream() .filter(Objects::nonNull) .map(ModContainer::getModId) - .collect(Collectors.toSet()); + .collect(Collectors.toUnmodifiableSet()); } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 5fb7bb83d..f04956151 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -789,6 +789,7 @@ private String getFormattedModName(boolean favouriteIconVisible) { @NotNull private String getFormattedModVersion(boolean favouriteIconVisible) { String version = this.data.getVersion(); + if (version.isBlank()) version = this.data.getInnerVersion(); return TextFormatting.GRAY + this.getFormattedText(version, favouriteIconVisible); } @@ -921,12 +922,12 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { // Draw version String displayVersion = this.selectedModData.getVersion(); - this.drawStringWithLabel("catalogue.gui.version", displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); - - // Draw inner version if the display version is different from it - int versionWidth = this.fontRenderer.getStringWidth(I18n.format("catalogue.gui.version", displayVersion)); String innerVersion = this.selectedModData.getInnerVersion(); - if (!displayVersion.equals(innerVersion) && ClientHelper.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { + boolean useInnerAsMain = displayVersion.isBlank() && !innerVersion.isBlank(); + int versionWidth = this.drawStringWithLabel(useInnerAsMain ? "catalogue.gui.inner_version" : "catalogue.gui.version", useInnerAsMain ? innerVersion : displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + + // Draw inner version tool tip if the display version is different from it + if (!useInnerAsMain && !displayVersion.equals(innerVersion) && !innerVersion.isBlank() && ClientHelper.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { this.setActiveTooltip(I18n.format("catalogue.gui.inner_version", innerVersion)); } @@ -1069,15 +1070,16 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, * @param mouseY the current mouse y position */ @SuppressWarnings("SameParameterValue") - private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { + private int drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { String formatted = labelColor + I18n.format(format, contentColor + text); if (this.fontRenderer.getStringWidth(formatted) > maxWidth) { formatted = this.fontRenderer.trimStringToWidth(formatted, maxWidth - 7) + "..."; - if (ClientHelper.isMouseWithin(x, y, maxWidth, 9, mouseX, mouseY)) { // Sets the active tool tip if string is too long so users can still read it + // Sets the active tool tip if string is too long so users can still read it + if (ClientHelper.isMouseWithin(x, y, maxWidth, 9, mouseX, mouseY)) { this.setActiveTooltip(text); } } - drawString(this.fontRenderer, formatted, x, y, 0xFFFFFF); + return this.fontRenderer.drawStringWithShadow(formatted, x, y, 0xFFFFFF); } private ImageInfo getBanner(String modId) { From 6e37d0320923d138527d633f6b74d0f044675aa5 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 30 Jan 2026 20:07:11 +0800 Subject: [PATCH 75/91] Fix width calculation --- .../client/screen/CatalogueModListScreen.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index f04956151..054159d9a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -370,7 +370,10 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti // Version check button if (this.selectedModData != null) { int contentLeft = this.modList.right + 12 + 10; - String version = I18n.format("catalogue.gui.version", this.selectedModData.getVersion()); + String displayVersion = this.selectedModData.getVersion(); + String innerVersion = this.selectedModData.getInnerVersion(); + boolean useInnerAsMain = displayVersion.isBlank() && !innerVersion.isBlank(); + String version = I18n.format(useInnerAsMain ? "catalogue.gui.inner_version" : "catalogue.gui.version", useInnerAsMain ? innerVersion : displayVersion); int versionWidth = this.fontRenderer.getStringWidth(version); if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { IModData.Update update = this.selectedModData.getUpdate(); @@ -924,7 +927,10 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { String displayVersion = this.selectedModData.getVersion(); String innerVersion = this.selectedModData.getInnerVersion(); boolean useInnerAsMain = displayVersion.isBlank() && !innerVersion.isBlank(); - int versionWidth = this.drawStringWithLabel(useInnerAsMain ? "catalogue.gui.inner_version" : "catalogue.gui.version", useInnerAsMain ? innerVersion : displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + String drawKey = useInnerAsMain ? "catalogue.gui.inner_version" : "catalogue.gui.version"; + String drawVersion = useInnerAsMain ? innerVersion : displayVersion; + this.drawStringWithLabel(drawKey, drawVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); + int versionWidth = this.fontRenderer.getStringWidth(I18n.format(drawKey, drawVersion)); // Draw inner version tool tip if the display version is different from it if (!useInnerAsMain && !displayVersion.equals(innerVersion) && !innerVersion.isBlank() && ClientHelper.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { @@ -1070,7 +1076,7 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, * @param mouseY the current mouse y position */ @SuppressWarnings("SameParameterValue") - private int drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { + private void drawStringWithLabel(String format, String text, int x, int y, int maxWidth, int mouseX, int mouseY, TextFormatting labelColor, TextFormatting contentColor) { String formatted = labelColor + I18n.format(format, contentColor + text); if (this.fontRenderer.getStringWidth(formatted) > maxWidth) { formatted = this.fontRenderer.trimStringToWidth(formatted, maxWidth - 7) + "..."; @@ -1079,7 +1085,7 @@ private int drawStringWithLabel(String format, String text, int x, int y, int ma this.setActiveTooltip(text); } } - return this.fontRenderer.drawStringWithShadow(formatted, x, y, 0xFFFFFF); + drawString(this.fontRenderer, formatted, x, y, 0xFFFFFF); } private ImageInfo getBanner(String modId) { From 84f599fecaec03ce85fce13a9d1981dac7296f56 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:27:10 +0800 Subject: [PATCH 76/91] Handle some of the review suggestions Also update some old links and add some new keys, copied from 1.20.1 forge toml. --- .../catalogue/CatalogueConfig.java | 6 ++-- .../java/com/cleanroommc/catalogue/Utils.java | 5 ---- .../catalogue/client/CleanroomModData.java | 4 ++- .../catalogue/client/ClientHandler.java | 3 +- .../catalogue/client/IModData.java | 1 + .../client/screen/CatalogueModListScreen.java | 2 ++ .../client/screen/MinecraftModData.java | 4 +-- .../client/screen/layout/GridLayout.java | 9 +++--- .../client/screen/layout/LinearLayout.java | 5 ++-- .../common/CleanroomContainer.java | 4 +++ .../common/ForgeModContainer.java | 28 ++++++++++--------- .../fml/client/FMLClientHandler.java | 3 +- .../fml/client/GuiErrorBase.java | 3 +- .../minecraftforge/fml/client/GuiModList.java | 3 -- .../net/minecraftforge/fml/common/Loader.java | 3 -- .../fml/common/MinecraftDummyContainer.java | 4 +-- .../assets/catalogue/lang/en_us.lang | 4 +-- .../assets/catalogue/lang/zh_cn.lang | 4 +-- 18 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index 400e741ea..e14ecd3d9 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -10,11 +10,11 @@ public class CatalogueConfig { @Config.Comment({ - "Whether enable Catalogue mod.", + "Whether enable Catalogue.", "Setting it false will stop Catalogue redirecting Forge's mod list calls." }) - @Config.LangKey("catalogue.config.enable_mod") - public static boolean enableMod = true; + @Config.LangKey("catalogue.config.enable") + public static boolean enable = true; @Config.RequiresMcRestart @Config.Comment({ diff --git a/src/main/java/com/cleanroommc/catalogue/Utils.java b/src/main/java/com/cleanroommc/catalogue/Utils.java index 977b45da0..a48c1b8ff 100644 --- a/src/main/java/com/cleanroommc/catalogue/Utils.java +++ b/src/main/java/com/cleanroommc/catalogue/Utils.java @@ -16,11 +16,6 @@ public static ResourceLocation withDefaultNamespace(String name) { return resource("textures/gui/sprites/" + name + ".png"); } - public static T make(T object, Consumer consumer) { - consumer.accept(object); - return object; - } - public static float lerp(float delta, float start, float end) { return start + delta * (end - start); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index 8978e855d..10c7258f7 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -35,6 +35,7 @@ public class CleanroomModData implements IModData { private final Type type; private final Set dependencies; private final Set childMods; + private final String modId; public CleanroomModData(@NotNull ModContainer info) { this.info = info; @@ -42,6 +43,7 @@ public CleanroomModData(@NotNull ModContainer info) { this.type = analyzeType(info); this.dependencies = analyzeDependencies(info); this.childMods = analyzeChildMods(info); + this.modId = this.info.getModId(); } @Override @@ -51,7 +53,7 @@ public Type getType() { @Override public String getModId() { - return this.info.getModId(); + return this.modId; } @Override diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java index 40faef8d0..80891e020 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java @@ -13,8 +13,7 @@ public class ClientHandler { @SubscribeEvent public static void onOpenScreen(@NotNull GuiOpenEvent event) { - if (!CatalogueConfig.enableMod) return; - //noinspection deprecation + if (!CatalogueConfig.enable) return; if (event.getGui() instanceof GuiModList screen) { event.setGui(new CatalogueModListScreen(screen.getParentScreen())); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/IModData.java b/src/main/java/com/cleanroommc/catalogue/client/IModData.java index 02af3a310..0b9d65f07 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/IModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/IModData.java @@ -11,6 +11,7 @@ import java.util.Set; /** + *

    Experimental

    * Author: MrCrayfish */ public interface IModData { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 054159d9a..4b58bea8b 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -361,11 +361,13 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti // Mod List if (this.modList.mouseClicked(mouseX, mouseY, button)) return; + /* // Catalogue button if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY) && button == 0) { this.openLink("https://www.curseforge.com/minecraft/mc-mods/catalogue"); return; } + */ // Version check button if (this.selectedModData != null) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java index f0cc524be..2c3627a4a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java @@ -62,7 +62,7 @@ public String getImageIcon() { @Nullable @Override public String getLicense() { - return "All Rights Reserved"; + return "All Rights Reserved (https://www.minecraft.net/en-us/eula)"; } @Nullable @@ -86,7 +86,7 @@ public String getHomepage() { @Nullable @Override public String getIssueTracker() { - return "https://bugs.mojang.com/projects/MC/issues"; + return "https://bugs.mojang.com/browse/MC"; } @Nullable diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/GridLayout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/GridLayout.java index 4c88f288f..7559d9256 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/GridLayout.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/GridLayout.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import java.util.function.UnaryOperator; @SideOnly(Side.CLIENT) public class GridLayout extends AbstractLayout { @@ -99,8 +100,8 @@ public T addChild(T child, int row, int column, Layout return this.addChild(child, row, column, 1, 1, layoutSettings); } - public T addChild(T child, int row, int column, Consumer layoutSettingsFactory) { - return this.addChild(child, row, column, 1, 1, Utils.make(this.newCellSettings(), layoutSettingsFactory)); + public T addChild(T child, int row, int column, UnaryOperator layoutSettingsFactory) { + return this.addChild(child, row, column, 1, 1, layoutSettingsFactory.apply(this.newCellSettings())); } public T addChild(T child, int row, int column, int occupiedRows, int occupiedColumns) { @@ -119,8 +120,8 @@ public T addChild(T child, int row, int column, int oc } } - public T addChild(T child, int row, int column, int occupiedRows, int occupiedColumns, Consumer layoutSettingsFactory) { - return this.addChild(child, row, column, occupiedRows, occupiedColumns, Utils.make(this.newCellSettings(), layoutSettingsFactory)); + public T addChild(T child, int row, int column, int occupiedRows, int occupiedColumns, UnaryOperator layoutSettingsFactory) { + return this.addChild(child, row, column, occupiedRows, occupiedColumns, layoutSettingsFactory.apply(this.newCellSettings())); } public GridLayout columnSpacing(int columnSpacing) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java index f714f68f9..90afa38ed 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java @@ -5,6 +5,7 @@ import net.minecraftforge.fml.relauncher.SideOnly; import java.util.function.Consumer; +import java.util.function.UnaryOperator; @SideOnly(Side.CLIENT) public class LinearLayout implements Layout { @@ -42,8 +43,8 @@ public T addChild(T child) { return this.addChild(child, this.newCellSettings()); } - public T addChild(T child, Consumer layoutSettingsFactory) { - return this.orientation.addChild(this.wrapped, child, this.nextChildIndex++, Utils.make(this.newCellSettings(), layoutSettingsFactory)); + public T addChild(T child, UnaryOperator layoutSettingsFactory) { + return this.orientation.addChild(this.wrapped, child, this.nextChildIndex++, layoutSettingsFactory.apply(this.newCellSettings())); } @Override diff --git a/src/main/java/com/cleanroommc/common/CleanroomContainer.java b/src/main/java/com/cleanroommc/common/CleanroomContainer.java index 35dbc275f..d65ea201d 100644 --- a/src/main/java/com/cleanroommc/common/CleanroomContainer.java +++ b/src/main/java/com/cleanroommc/common/CleanroomContainer.java @@ -23,6 +23,10 @@ public CleanroomContainer() { """; meta.version = CleanroomVersion.VERSION; meta.authorList = Arrays.asList("LexManos", "cpw", "fry", "Rongmario", "kappa_maintainer", "Li"); + meta.credits = "Thanks to MrCrayFish for his Catalogue mod for the backbone of our revamped mod list screen."; + meta.url = "https://cleanroommc.com"; + meta.issueTrackerUrl = "https://github.com/CleanroomMC/Cleanroom/issues"; + meta.license = "LGPL v2.1"; meta.updateJSON = "https://download.cleanroommc.com/api/forge"; meta.logoFile = "/cleanroom_banner.png"; meta.iconFile = "/cleanroom_icon.png"; diff --git a/src/main/java/net/minecraftforge/common/ForgeModContainer.java b/src/main/java/net/minecraftforge/common/ForgeModContainer.java index fd5f448f4..058078560 100644 --- a/src/main/java/net/minecraftforge/common/ForgeModContainer.java +++ b/src/main/java/net/minecraftforge/common/ForgeModContainer.java @@ -145,20 +145,22 @@ public static ForgeModContainer getInstance() public ForgeModContainer() { super(new ModMetadata()); - ModMetadata meta = getMetadata(); - meta.modId = ForgeVersion.MOD_ID; - meta.name = "Minecraft Forge"; - meta.version = ForgeVersion.getVersion(); - meta.credits = "Made possible with help from many people"; - meta.authorList = Arrays.asList("LexManos", "cpw", "fry"); - meta.description = "Minecraft Forge is a common open source API allowing a broad range of mods " + - "to work cooperatively together. It allows many mods to be created without " + - "them editing the main Minecraft code."; - meta.url = "http://minecraftforge.net"; - meta.screenshots = new String[0]; - meta.logoFile = "/forge_logo.png"; + ModMetadata meta = getMetadata(); + meta.modId = ForgeVersion.MOD_ID; + meta.name = "Minecraft Forge"; + meta.version = ForgeVersion.getVersion(); + meta.credits = "Anyone who has contributed on Github and supports our development"; + meta.authorList = Arrays.asList("LexManos", "cpw", "fry"); + meta.description = """ + Forge, a broad compatibility API. + """; + meta.url = "https://minecraftforge.net/"; + meta.issueTrackerUrl = "https://minecraftforge.net/"; + meta.license = "LGPL v2.1"; + meta.screenshots = new String[0]; + meta.logoFile = "/forge_logo.png"; try { - updateJSONUrl = new URI("http://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json").toURL(); + updateJSONUrl = new URI("https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json").toURL(); } catch (MalformedURLException | URISyntaxException ignored) {} config = null; diff --git a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java index 6108f08f6..af9a8157e 100644 --- a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java +++ b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java @@ -35,6 +35,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import com.cleanroommc.catalogue.CatalogueConfig; import com.cleanroommc.kirino.KirinoClientCore; import com.cleanroommc.kirino.KirinoCommonCore; import net.minecraft.client.Minecraft; @@ -769,7 +770,7 @@ public void tryLoadExistingWorld(GuiWorldSelection selectWorldGUI, WorldSummary public void showInGameModOptions(GuiIngameMenu guiIngameMenu) { - showGuiScreen(new CatalogueModListScreen(guiIngameMenu)); + showGuiScreen(CatalogueConfig.enable ? new CatalogueModListScreen(guiIngameMenu) : new GuiModList(guiIngameMenu)); } public IModGuiFactory getGuiFactoryFor(ModContainer selectedMod) diff --git a/src/main/java/net/minecraftforge/fml/client/GuiErrorBase.java b/src/main/java/net/minecraftforge/fml/client/GuiErrorBase.java index 8bc6f944d..038749580 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiErrorBase.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiErrorBase.java @@ -59,7 +59,8 @@ protected void actionPerformed(GuiButton button) { try { - Desktop.getDesktop().open(Loader.instance().getModsDir()); + File modsDir = new File(minecraftDir, "mods"); + Desktop.getDesktop().open(modsDir); } catch (Exception e) { diff --git a/src/main/java/net/minecraftforge/fml/client/GuiModList.java b/src/main/java/net/minecraftforge/fml/client/GuiModList.java index 181c0b806..e0ccd0d19 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiModList.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiModList.java @@ -69,10 +69,7 @@ /** * @author cpw - * @deprecated Use {@link com.cleanroommc.catalogue.client.screen.CatalogueModListScreen} */ -@Deprecated -@SuppressWarnings("DeprecatedIsStillUsed") public class GuiModList extends GuiScreen { private enum SortType implements Comparator diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index eba261bd7..d0bd8a98b 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -172,9 +172,6 @@ public class Loader * The canonical configuration directory */ private File canonicalConfigDir; - /** - * The canonical mods directory - */ private File canonicalModsDir; private LoadController modController; private MinecraftDummyContainer minecraft; diff --git a/src/main/java/net/minecraftforge/fml/common/MinecraftDummyContainer.java b/src/main/java/net/minecraftforge/fml/common/MinecraftDummyContainer.java index 1318d3a1f..4a25297de 100644 --- a/src/main/java/net/minecraftforge/fml/common/MinecraftDummyContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/MinecraftDummyContainer.java @@ -46,8 +46,8 @@ public MinecraftDummyContainer(String actualMCVersion) meta.description = "Minecraft is a 3D sandbox adventure game developed by Mojang Studios where players can interact with a fully customizable three-dimensional world made of blocks and entities. Its diverse gameplay options allow players to choose the way they play, creating countless possibilities."; meta.authorList = List.of("Mojang AB"); meta.url = "https://www.minecraft.net"; - meta.issueTrackerUrl = "https://bugs.mojang.com/projects/MC/issues"; - meta.license = "All Rights Reserved"; + meta.issueTrackerUrl = "https://bugs.mojang.com/browse/MC"; + meta.license = "All Rights Reserved (https://www.minecraft.net/en-us/eula)"; staticRange = VersionParser.parseRange("["+actualMCVersion+"]"); } diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index b0d0579bc..07648c644 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -44,8 +44,8 @@ catalogue.gui.website=Website catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s -catalogue.config.enable_mod=Enable Mod -catalogue.config.enable_mod.tooltip=Whether enable Catalogue mod. \nSetting it false will stop Catalogue redirecting Forge's mod list calls. +catalogue.config.enable_mod=Enable +catalogue.config.enable_mod.tooltip=Whether enable Catalogue. \nSetting it false will stop Catalogue redirecting Forge's mod list calls. catalogue.config.library_list=Library List catalogue.config.library_list.tooltip=The list of library mods' mod ids.\nThey will have grey names in the mod list. catalogue.config.ignored_dependencies_list=Ignored Dependencies List diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index d9609612d..f164e0650 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -44,8 +44,8 @@ catalogue.gui.website=网站 catalogue.gui.submit_bug=提交Bug catalogue.gui.mod_id=Mod ID:%s -catalogue.config.enable_mod=启用模组 -catalogue.config.enable_mod.tooltip=控制是否启用Catalogue模组。 \n设置为否会阻止Catalogue重定向Forge模组列表的调用。 +catalogue.config.enable_mod=启用 +catalogue.config.enable_mod.tooltip=控制是否启用Catalogue。 \n设置为否会阻止Catalogue重定向Forge模组列表的调用。 catalogue.config.library_list=库列表 catalogue.config.library_list.tooltip=库模组的Mod ID列表。\n它们会在模组列表中显示灰色名字。 catalogue.config.ignored_dependencies_list=忽略前置列表 From bc8b537e932ed50bd2716b574bf024051f5e2036 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:41:13 +0800 Subject: [PATCH 77/91] Add kirino to library list --- src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java index e14ecd3d9..dfabbd1db 100644 --- a/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java +++ b/src/main/java/com/cleanroommc/catalogue/CatalogueConfig.java @@ -30,7 +30,10 @@ public class CatalogueConfig { "configanytime", "mixinbooter", "fugue", - "scalar" + "scalar", + "kirino_ecs", + "kirino_engine", + "kirino_gl" }; @Config.RequiresMcRestart From 45cde05590dbc63b3efbe068653528dee7af9612 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 31 Jan 2026 16:55:40 +0800 Subject: [PATCH 78/91] Format --- .../catalogue/client/screen/CatalogueModListScreen.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 4b58bea8b..35483b289 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -788,10 +788,9 @@ private void drawIcon(int top, int left) { private String getFormattedModName(boolean favouriteIconVisible) { String name = this.data.getDisplayName(); name = this.getFormattedText(name, favouriteIconVisible); - return data.getType().getStyle() + name; + return this.data.getType().getStyle() + name; } - @NotNull private String getFormattedModVersion(boolean favouriteIconVisible) { String version = this.data.getVersion(); if (version.isBlank()) version = this.data.getInnerVersion(); From 1188f277dc0f3bf00a441bf813398f0f165ff78e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:09:49 +0800 Subject: [PATCH 79/91] Use new switch --- .../client/screen/CatalogueModListScreen.java | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 35483b289..7fe4b0354 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -213,10 +213,8 @@ public void onGuiClosed() { @Override public void actionPerformed(@NotNull GuiButton button) { switch (button.id) { - case 1: - this.mc.displayGuiScreen(this.parentScreen); - break; - case 2: + case 1 -> this.mc.displayGuiScreen(this.parentScreen); + case 2 -> { try { Class oclass = Class.forName("java.awt.Desktop"); Object object = oclass.getMethod("getDesktop", new Class[0]).invoke(null); @@ -224,17 +222,11 @@ public void actionPerformed(@NotNull GuiButton button) { } catch (Exception e) { CatalogueConstants.LOG.error("Problem opening mods folder", e); } - break; - case 3: - this.selectedModData.openConfigScreen(this.mc, this); - break; - case 4: - this.openLink(this.selectedModData.getHomepage()); - break; - case 5: - this.openLink(this.selectedModData.getIssueTracker()); - break; - case 6: + } + case 3 -> this.selectedModData.openConfigScreen(this.mc, this); + case 4 -> this.openLink(this.selectedModData.getHomepage()); + case 5 -> this.openLink(this.selectedModData.getIssueTracker()); + case 6 -> { DropdownMenu menu = DropdownMenu.builder(this) .setMinItemSize(100, 16) .setAlignment(DropdownMenu.Alignment.BELOW_RIGHT) @@ -277,9 +269,7 @@ public void actionPerformed(@NotNull GuiButton button) { return false; }).build(); menu.toggle(button); - break; - default: - break; + } } } From f88b394cdd2b6903c1b99f42c534d4079fe20199 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:43:44 +0800 Subject: [PATCH 80/91] Fix suggestion sometimes not updated --- .../catalogue/client/screen/CatalogueModListScreen.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 7fe4b0354..587fd16d8 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -162,7 +162,6 @@ public int getWidth() { this.searchTextField.setResponder(s -> { if (!OPTION_QUERY.get().equals(s)) { OPTION_QUERY.setValue(s); - this.updateSearchFieldSuggestion(s); this.modList.filterAndUpdateList(); } }); @@ -201,7 +200,6 @@ public int getWidth() { this.modList.centerScrollOn(entry); } } - this.updateSearchFieldSuggestion(this.searchTextField.getText()); } @Override @@ -526,6 +524,7 @@ public void filterAndUpdateList() { Optional selectedEntry = this.children().stream().filter(entry -> entry.data == CatalogueModListScreen.this.selectedModData).findFirst(); selectedEntry.ifPresent(this::setSelected); } + CatalogueModListScreen.this.updateSearchFieldSuggestion(); this.clampAmountScrolled(); } @@ -1272,7 +1271,8 @@ private int getFooterTextElementCount(@NotNull IModData data) { return count; } - private void updateSearchFieldSuggestion(@NotNull String value) { + private void updateSearchFieldSuggestion() { + String value = this.searchTextField.getText(); if (value.isEmpty()) { this.searchTextField.setSuggestion(I18n.format("catalogue.gui.search") + "..."); } else if (value.startsWith("@")) { From dac3d26af86a7417873b1895b9d7e680760ac16e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 31 Jan 2026 18:51:44 +0800 Subject: [PATCH 81/91] Optimize --- .../catalogue/client/screen/CatalogueModListScreen.java | 2 +- .../catalogue/client/screen/widget/CatalogueTextButton.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 587fd16d8..095985b98 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -1167,7 +1167,7 @@ private void drawBackground(int contentWidth, int contentLeft, int contentTop) { GlStateManager.enableBlend(); GlStateManager.disableAlpha(); GlStateManager.shadeModel(GL11.GL_SMOOTH); - GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); Tessellator tessellator = Tessellator.getInstance(); BufferBuilder builder = tessellator.getBuffer(); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java index 4bd85331a..e3007781c 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java @@ -24,7 +24,6 @@ public void drawButton(@NotNull Minecraft mc, int mouseX, int mouseY, float part GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); - GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); mc.getTextureManager().bindTexture(SPRITES.get(this.enabled, this.hovered)); ClientHelper.blitNineSlicedSprite(new ClientHelper.NineSlice(200, 20, 3), this.x, this.y, this.width, this.height); From 4789d0681c812902f963541f880157529a8fc45d Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:40:33 +0800 Subject: [PATCH 82/91] Polish author headers --- src/main/java/com/cleanroommc/catalogue/client/Branding.java | 4 +--- .../com/cleanroommc/catalogue/client/CleanroomModData.java | 4 +--- .../java/com/cleanroommc/catalogue/client/ClientHandler.java | 4 +--- .../java/com/cleanroommc/catalogue/client/ClientHelper.java | 4 +--- src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java | 4 +--- .../java/com/cleanroommc/catalogue/client/ImagePredicate.java | 4 +--- .../catalogue/client/screen/DropdownMenuHandler.java | 4 +--- .../cleanroommc/catalogue/client/screen/MinecraftModData.java | 4 +--- .../catalogue/client/screen/layout/BorderedLinearLayout.java | 4 +--- .../catalogue/client/screen/widget/CatalogueIconButton.java | 4 +--- .../catalogue/client/screen/widget/DropdownMenu.java | 4 +--- .../catalogue/platform/CleanroomPlatformHelper.java | 4 +--- 12 files changed, 12 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/Branding.java b/src/main/java/com/cleanroommc/catalogue/client/Branding.java index b2c557d36..5d8c5856b 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/Branding.java +++ b/src/main/java/com/cleanroommc/catalogue/client/Branding.java @@ -16,9 +16,7 @@ import java.util.function.BiPredicate; import java.util.function.Function; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public record Branding(String prefix, int imageWidth, int imageHeight, BiPredicate predicate, Function locator, boolean override) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index 10c7258f7..9c4612b4f 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -22,9 +22,7 @@ import java.util.*; import java.util.stream.Collectors; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public class CleanroomModData implements IModData { public static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation("forge", "textures/gui/version_check_icons.png"); public static final List LIB_MODS = Arrays.asList(CatalogueConfig.libraryList); diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java index 80891e020..fc94c0bdb 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHandler.java @@ -7,9 +7,7 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import org.jetbrains.annotations.NotNull; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public class ClientHandler { @SubscribeEvent public static void onOpenScreen(@NotNull GuiOpenEvent event) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java index cd1f66461..8fe722cba 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ClientHelper.java @@ -7,9 +7,7 @@ import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import org.lwjgl.opengl.GL11; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public class ClientHelper { /** * Creates a scissor test using minecraft screen coordinates instead of pixel coordinates. diff --git a/src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java b/src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java index df746526e..c1bb3f463 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ImageInfo.java @@ -2,8 +2,6 @@ import net.minecraft.util.ResourceLocation; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public record ImageInfo(ResourceLocation resource, int width, int height, Runnable unregister) { } diff --git a/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java b/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java index 6500d976d..9984d9c0a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java +++ b/src/main/java/com/cleanroommc/catalogue/client/ImagePredicate.java @@ -9,9 +9,7 @@ import java.util.function.BiFunction; import java.util.function.BiPredicate; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public record ImagePredicate(TriFunction predicate, BiFunction errorMessage) implements BiPredicate { public static final ImagePredicate SQUARE = new ImagePredicate((image, maxWidth, maxHeight) -> Objects.equals(image.getWidth(), image.getHeight()), (maxWidth, maxHeight) -> "Image must be a square"); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/DropdownMenuHandler.java b/src/main/java/com/cleanroommc/catalogue/client/screen/DropdownMenuHandler.java index 626ced855..22b5c121a 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/DropdownMenuHandler.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/DropdownMenuHandler.java @@ -3,9 +3,7 @@ import com.cleanroommc.catalogue.client.screen.widget.DropdownMenu; import org.jetbrains.annotations.Nullable; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public interface DropdownMenuHandler { void setMenu(@Nullable DropdownMenu menu); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java index 2c3627a4a..e85ddbc7b 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/MinecraftModData.java @@ -11,9 +11,7 @@ import java.util.Collections; import java.util.Set; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public class MinecraftModData implements IModData { @Override public Type getType() { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/BorderedLinearLayout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/BorderedLinearLayout.java index 42ee94e2c..34b3b0d76 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/BorderedLinearLayout.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/BorderedLinearLayout.java @@ -1,8 +1,6 @@ package com.cleanroommc.catalogue.client.screen.layout; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public class BorderedLinearLayout extends LinearLayout { private int border; diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index 737f42398..35baed2f7 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -7,9 +7,7 @@ import net.minecraft.util.ResourceLocation; import org.jetbrains.annotations.NotNull; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public class CatalogueIconButton extends CatalogueTextButton { public static final ResourceLocation TEXTURE = new ResourceLocation(CatalogueConstants.MOD_ID, "textures/gui/icons.png"); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java index 9418b5e39..87164d7e5 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java @@ -26,9 +26,7 @@ import java.util.function.Consumer; import java.util.function.Function; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public class DropdownMenu extends Gui implements LayoutElement { private final DropdownMenuHandler handler; private final BorderedLinearLayout layout = (BorderedLinearLayout) diff --git a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java index c3b9d07ba..b3438b424 100644 --- a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java @@ -26,9 +26,7 @@ import java.util.List; import java.util.stream.Collectors; -/** - * Author: MrCrayfish - */ +/// @author MrCrayfish public class CleanroomPlatformHelper implements IPlatformHelper { @Override From 44f84472d58ecc164991f3a646b4f2f776c7d172 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:42:44 +0800 Subject: [PATCH 83/91] Format code --- .../java/com/cleanroommc/catalogue/Utils.java | 2 -- .../catalogue/client/CleanroomModData.java | 2 +- .../client/screen/CatalogueModListScreen.java | 28 +++++++++---------- .../client/screen/layout/LayoutElement.java | 4 +-- .../client/screen/layout/LayoutSettings.java | 2 +- .../client/screen/layout/LinearLayout.java | 3 +- .../client/screen/layout/ScreenRectangle.java | 4 +-- .../screen/widget/CatalogueIconButton.java | 2 +- .../screen/widget/CatalogueListSelection.java | 2 +- .../screen/widget/CatalogueTextButton.java | 10 +++---- .../screen/widget/CatalogueTextField.java | 4 +-- .../client/screen/widget/DropdownMenu.java | 4 +-- .../platform/CleanroomPlatformHelper.java | 2 +- 13 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/Utils.java b/src/main/java/com/cleanroommc/catalogue/Utils.java index a48c1b8ff..2c5161c3f 100644 --- a/src/main/java/com/cleanroommc/catalogue/Utils.java +++ b/src/main/java/com/cleanroommc/catalogue/Utils.java @@ -2,8 +2,6 @@ import net.minecraft.util.ResourceLocation; -import java.util.function.Consumer; - /** * Author: MrCrayfish */ diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index 9c4612b4f..ce28a9fb9 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -38,7 +38,7 @@ public class CleanroomModData implements IModData { public CleanroomModData(@NotNull ModContainer info) { this.info = info; this.metadata = info.getMetadata(); - this.type = analyzeType(info); + this.type = this.analyzeType(info); this.dependencies = analyzeDependencies(info); this.childMods = analyzeChildMods(info); this.modId = this.info.getModId(); diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index 095985b98..c30b6d67c 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -169,8 +169,8 @@ public int getWidth() { this.modList = new ModList(); this.modList.setSlotXBoundsFromLeft(10); - this.addButton(new CatalogueTextButton(1, 10, modList.bottom + 8, 127, 20, I18n.format("gui.back"))); - this.modFolderButton = this.addButton(new CatalogueIconButton(2, 140, modList.bottom + 8, 0, 0)); + this.addButton(new CatalogueTextButton(1, 10, this.modList.bottom + 8, 127, 20, I18n.format("gui.back"))); + this.modFolderButton = this.addButton(new CatalogueIconButton(2, 140, this.modList.bottom + 8, 0, 0)); int padding = 10; int contentLeft = this.modList.right + 12 + padding; @@ -448,7 +448,7 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { String title = modsLabel + " " + countLabel; int titleWidth = this.fontRenderer.getStringWidth(title); int titleLeft = this.modList.left + (this.modList.width - titleWidth) / 2; - drawString(this.fontRenderer, title, titleLeft, 10, 0xFFFFFF); + this.drawString(this.fontRenderer, title, titleLeft, 10, 0xFFFFFF); int countLabelWidth = this.fontRenderer.getStringWidth(countLabel); if (ClientHelper.isMouseWithin(titleLeft + titleWidth - countLabelWidth, 10, countLabelWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { @@ -508,7 +508,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { String text = I18n.format("catalogue.gui.no_mods"); int left = this.left + this.width / 2; int top = this.top + (this.bottom - this.top - CatalogueModListScreen.this.fontRenderer.FONT_HEIGHT) / 2; - drawCenteredString(CatalogueModListScreen.this.fontRenderer, text, left, top, 0xFFFFFFFF); + CatalogueModListScreen.this.drawCenteredString(CatalogueModListScreen.this.fontRenderer, text, left, top, 0xFFFFFFFF); } } @@ -631,8 +631,8 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, // Draws mod name and version boolean inOptionsMenu = CatalogueModListScreen.this.menu != null; boolean drawFavouriteIcon = !inOptionsMenu && this.data.getType() != IModData.Type.CHILD && !this.list.shouldHideFavourites() && ClientHelper.isMouseWithin(left + rowWidth - rowHeight - 4, top, rowHeight + 4, rowHeight, mouseX, mouseY) || FAVOURITES.has(this.data.getModId()); - drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModName(drawFavouriteIcon), left + 24, top + 2, 0xFFFFFF); - drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModVersion(drawFavouriteIcon), left + 24, top + 12, 0xFFFFFF); + CatalogueModListScreen.this.drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModName(drawFavouriteIcon), left + 24, top + 2, 0xFFFFFF); + CatalogueModListScreen.this.drawString(CatalogueModListScreen.this.fontRenderer, this.getFormattedModVersion(drawFavouriteIcon), left + 24, top + 12, 0xFFFFFF); // Draw image icon or fallback to item icon this.drawIcon(top, left); @@ -838,7 +838,7 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseButt if (this.button.mousePressed(CatalogueModListScreen.this.mc, mouseX, mouseY)) { FAVOURITES.toggle(this.data.getModId()); ModListEntry.this.list.filterAndUpdateList(); - this.button.playPressSound(mc.getSoundHandler()); + this.button.playPressSound(CatalogueModListScreen.this.mc.getSoundHandler()); return true; } CatalogueModListScreen.this.setSelectedModData(this.data); @@ -905,13 +905,13 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { GlStateManager.pushMatrix(); GlStateManager.translate(contentLeft, 70, 0); GlStateManager.scale(2.0F, 2.0F, 2.0F); - drawString(this.fontRenderer, this.selectedModData.getDisplayName(), 0, 0, 0xFFFFFF); + this.drawString(this.fontRenderer, this.selectedModData.getDisplayName(), 0, 0, 0xFFFFFF); GlStateManager.popMatrix(); // Draw mod id String modId = TextFormatting.DARK_GRAY + I18n.format("catalogue.gui.mod_id", this.selectedModData.getModId()); int modIdWidth = this.fontRenderer.getStringWidth(modId); - drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); + this.drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); // Draw version String displayVersion = this.selectedModData.getVersion(); @@ -938,7 +938,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { } // Draw fade from the bottom - drawGradientRect(listRight + 12, this.height - 50, this.width, this.height, 0x00000000, 0x66000000); + this.drawGradientRect(listRight + 12, this.height - 50, this.width, this.height, 0x00000000, 0x66000000); int labelOffset = this.height - 18; @@ -976,7 +976,7 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { } } else { String message = TextFormatting.GRAY + I18n.format("catalogue.gui.no_selection"); - drawCenteredString(this.fontRenderer, message, contentLeft + contentWidth / 2, this.height / 2 - 5, 0xFFFFFF); + this.drawCenteredString(this.fontRenderer, message, contentLeft + contentWidth / 2, this.height / 2 - 5, 0xFFFFFF); } } @@ -1048,7 +1048,7 @@ public StringEntry(String line) { @Override public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { - drawString(CatalogueModListScreen.this.fontRenderer, this.line, left, top, 0xFFFFFF); + CatalogueModListScreen.this.drawString(CatalogueModListScreen.this.fontRenderer, this.line, left, top, 0xFFFFFF); } } @@ -1075,7 +1075,7 @@ private void drawStringWithLabel(String format, String text, int x, int y, int m this.setActiveTooltip(text); } } - drawString(this.fontRenderer, formatted, x, y, 0xFFFFFF); + this.drawString(this.fontRenderer, formatted, x, y, 0xFFFFFF); } private ImageInfo getBanner(String modId) { @@ -1358,7 +1358,7 @@ private void load() { this.init(); this.mods.clear(); Predicate modIdRegex = MOD_ID_PATTERN.asMatchPredicate(); - Files.readAllLines(file).forEach(s -> { + Files.readAllLines(this.file).forEach(s -> { if (modIdRegex.test(s) && ClientServices.PLATFORM.isModLoaded(s)) { this.mods.add(s); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutElement.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutElement.java index b571531c6..f44d9655d 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutElement.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutElement.java @@ -20,8 +20,8 @@ public interface LayoutElement { int getHeight(); default void setPosition(int x, int y) { - setX(x); - setY(y); + this.setX(x); + this.setY(y); } default ScreenRectangle getRectangle() { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutSettings.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutSettings.java index d1b663c8e..a94562623 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutSettings.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LayoutSettings.java @@ -62,7 +62,7 @@ static LayoutSettings defaults() { } @SideOnly(Side.CLIENT) - public static class LayoutSettingsImpl implements LayoutSettings { + class LayoutSettingsImpl implements LayoutSettings { public int paddingLeft; public int paddingTop; public int paddingRight; diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java index 90afa38ed..c5ef97f71 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/LinearLayout.java @@ -1,6 +1,5 @@ package com.cleanroommc.catalogue.client.screen.layout; -import com.cleanroommc.catalogue.Utils; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -96,7 +95,7 @@ public static LinearLayout horizontal() { } @SideOnly(Side.CLIENT) - public static enum Orientation { + public enum Orientation { HORIZONTAL, VERTICAL; diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/ScreenRectangle.java b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/ScreenRectangle.java index d4311fc64..4ed7825f1 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/layout/ScreenRectangle.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/layout/ScreenRectangle.java @@ -2,10 +2,10 @@ public record ScreenRectangle(int left, int top, int width, int height) { public int right() { - return left + width; + return this.left + this.width; } public int bottom() { - return top + height; + return this.top + this.height; } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index 35baed2f7..55e6ba720 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -49,7 +49,7 @@ public void drawButton(@NotNull Minecraft minecraft, int mouseX, int mouseY, flo GlStateManager.color(brightness, brightness, brightness, 1.0F); drawModalRectWithCustomSizedTexture(iconX, iconY, this.u, this.v, 10, 10, 64, 64); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - drawString(fontrenderer, this.label, iconX + 14, iconY + 1, this.getFGColor()); + this.drawString(fontrenderer, this.label, iconX + 14, iconY + 1, this.getFGColor()); } } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListSelection.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListSelection.java index 21305fdd2..32d01647b 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListSelection.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueListSelection.java @@ -51,7 +51,7 @@ public void clearEntriesExcept(E entry) { @Override protected void renderItem(int slotIndex, int rowLeft, int rowTop, int rowRight, int rowBottom, int mouseX, int mouseY, float partialTicks) { if (this.showSelectionBox && this.isSelected(slotIndex)) { - renderSelection(rowLeft, rowTop, rowRight, rowBottom); + this.renderSelection(rowLeft, rowTop, rowRight, rowBottom); } super.renderItem(slotIndex, rowLeft, rowTop, rowRight, rowBottom, mouseX, mouseY, partialTicks); } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java index e3007781c..3a5c52867 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java @@ -38,11 +38,11 @@ public void renderString(FontRenderer font, int color) { protected void renderScrollingString(FontRenderer font, int width, int color) { int i = this.x + width; int j = this.x + this.width - width; - renderScrollingString(font, this.displayString, i, this.y, j, this.y + this.height, color); + this.renderScrollingString(font, this.displayString, i, this.y, j, this.y + this.height, color); } public void renderScrollingString(FontRenderer font, String text, int minX, int minY, int maxX, int maxY, int color) { - renderScrollingString(font, text, (minX + maxX) / 2, minX, minY, maxX, maxY, color); + this.renderScrollingString(font, text, (minX + maxX) / 2, minX, minY, maxX, maxY, color); } public void renderScrollingString(FontRenderer font, String text, int centerX, int minX, int minY, int maxX, int maxY, int color) { @@ -52,15 +52,15 @@ public void renderScrollingString(FontRenderer font, String text, int centerX, i if (i > k) { int l = i - k; double d0 = (double) System.currentTimeMillis() / (double) 1000.0F; - double d1 = Math.max((double) l * (double) 0.5F, (double) 3.0F); + double d1 = Math.max((double) l * (double) 0.5F, 3.0F); double d2 = Math.sin((Math.PI / 2D) * Math.cos((Math.PI * 2D) * d0 / d1)) / (double) 2.0F + (double) 0.5F; double d3 = Utils.lerp(d2, 0.0F, l); ClientHelper.scissor(minX, minY, maxX - minX, maxY - minY); - drawString(font, text, minX - (int) d3, j, color); + this.drawString(font, text, minX - (int) d3, j, color); GL11.glDisable(GL11.GL_SCISSOR_TEST); } else { int i1 = MathHelper.clamp(centerX, minX + i / 2, maxX - i / 2); - drawCenteredString(font, text, i1, j, color); + this.drawCenteredString(font, text, i1, j, color); } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java index 84e229f3d..eb210d662 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextField.java @@ -57,7 +57,7 @@ public void drawTextBox() { // Draw text before cursor if (!visibleText.isEmpty()) { String rawTextBeforeCursor = isCursorVisible ? visibleText.substring(0, cursorPosRelative) : visibleText; - currentDrawX = this.fontRenderer.drawStringWithShadow(formatText(rawTextBeforeCursor, this.lineScrollOffset), (float) textStartX, (float) textStartY, textColor); + currentDrawX = this.fontRenderer.drawStringWithShadow(this.formatText(rawTextBeforeCursor, this.lineScrollOffset), (float) textStartX, (float) textStartY, textColor); } this.isTextTruncated = this.cursorPosition < this.getText().length() || this.getText().length() >= this.getMaxStringLength(); @@ -73,7 +73,7 @@ public void drawTextBox() { // Draw text after cursor if (!visibleText.isEmpty() && isCursorVisible && cursorPosRelative < visibleText.length()) { String rawTextAfterCursor = visibleText.substring(cursorPosRelative); - currentDrawX = this.fontRenderer.drawStringWithShadow(formatText(rawTextAfterCursor, this.cursorPosition), (float) currentDrawX, (float) textStartY, textColor); + currentDrawX = this.fontRenderer.drawStringWithShadow(this.formatText(rawTextAfterCursor, this.cursorPosition), (float) currentDrawX, (float) textStartY, textColor); } if (!this.isTextTruncated && !this.suggestion.isEmpty()) { diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java index 87164d7e5..c63ae651e 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/DropdownMenu.java @@ -189,7 +189,7 @@ public void drawWidget(Minecraft minecraft, int mouseX, int mouseY, float deltaT FontRenderer font = minecraft.fontRenderer; int offset = (this.getHeight() - font.FONT_HEIGHT) / 2 + 1; - drawString(font, this.label, this.getX() + offset, this.getY() + offset, 0xFFFFFFFF); + this.drawString(font, this.label, this.getX() + offset, this.getY() + offset, 0xFFFFFFFF); } public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { @@ -334,7 +334,7 @@ public void drawWidget(Minecraft minecraft, int mouseX, int mouseY, float deltaT super.drawWidget(minecraft, mouseX, mouseY, deltaTick); FontRenderer font = minecraft.fontRenderer; int top = this.getY() + (this.getHeight() - font.FONT_HEIGHT) / 2 + 1; - drawString(font, ">", this.getX() + this.getWidth() - 10, top, 0xFFFFFFFF); + this.drawString(font, ">", this.getX() + this.getWidth() - 10, top, 0xFFFFFFFF); } @Override diff --git a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java index b3438b424..eb4b67a6d 100644 --- a/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java +++ b/src/main/java/com/cleanroommc/catalogue/platform/CleanroomPlatformHelper.java @@ -75,7 +75,7 @@ public Path getConfigDirectory() { @Override public BufferedImage loadImageFromModResource(String modid, String resource) throws IOException { - InputStream is = getClass().getResourceAsStream(resource); + InputStream is = this.getClass().getResourceAsStream(resource); return is != null ? TextureUtil.readBufferedImage(is) : null; } From 16de8f997858e045231845b2acc68025c98dce7e Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:47:00 +0800 Subject: [PATCH 84/91] Update CatalogueTextButton.java --- .../catalogue/client/screen/widget/CatalogueTextButton.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java index 3a5c52867..1f9ef0aac 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java @@ -68,11 +68,11 @@ protected int getFGColor() { if (this.packedFGColour != 0) { return this.packedFGColour; } else if (!this.enabled) { - return 10526880; + return 0xA0A0A0; } else if (this.hovered) { - return 16777120; + return 0xFFFFA0; } else { - return 14737632; + return 0xE0E0E0; } } } From 173a50a36a42f9829e4d9c8758e78d622a823c6c Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:53:54 +0800 Subject: [PATCH 85/91] Remove info tooltip --- .../catalogue/client/screen/CatalogueModListScreen.java | 2 ++ src/main/resources/assets/catalogue/lang/de_de.lang | 2 +- src/main/resources/assets/catalogue/lang/en_au.lang | 2 +- src/main/resources/assets/catalogue/lang/en_us.lang | 2 +- src/main/resources/assets/catalogue/lang/pl_pl.lang | 2 +- src/main/resources/assets/catalogue/lang/ru_ru.lang | 2 +- src/main/resources/assets/catalogue/lang/zh_cn.lang | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index c30b6d67c..bc4efc952 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -308,10 +308,12 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { if (this.menu != null) { this.menu.drawScreen(this.mc, mouseX, mouseY, partialTicks); } else { + /* if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { this.setActiveTooltip(I18n.format("catalogue.gui.info")); this.tooltipYOffset = 10; } + */ if (this.optionsButton.isMouseOver()) { this.setActiveTooltip(I18n.format("catalogue.gui.options")); diff --git a/src/main/resources/assets/catalogue/lang/de_de.lang b/src/main/resources/assets/catalogue/lang/de_de.lang index a3b9db1d0..ca6aaf7e9 100644 --- a/src/main/resources/assets/catalogue/lang/de_de.lang +++ b/src/main/resources/assets/catalogue/lang/de_de.lang @@ -1,2 +1,2 @@ catalogue.gui.no_selection=No mod selected... -catalogue.gui.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! +#catalogue.gui.info=This menu was redesigned by Catalogue. Click here to open the CurseForge page for this mod! diff --git a/src/main/resources/assets/catalogue/lang/en_au.lang b/src/main/resources/assets/catalogue/lang/en_au.lang index 5f9fb879d..7088bf07e 100644 --- a/src/main/resources/assets/catalogue/lang/en_au.lang +++ b/src/main/resources/assets/catalogue/lang/en_au.lang @@ -17,7 +17,7 @@ catalogue.gui.update_available=Update (%s): %s catalogue.gui.update_available_no_page=Update (%s) catalogue.gui.beta_update_available=Beta Update (%s): %s catalogue.gui.beta_update_available_no_page=Beta Update (%s) -catalogue.gui.info=This menu is provided by Catalogue. Click here to open the CurseForge page for this mod! +#catalogue.gui.info=This menu is provided by Catalogue. Click here to open the CurseForge page for this mod! catalogue.gui.favourite=Mark as Favourite catalogue.gui.remove_favourite=Remove as Favourite catalogue.gui.options=Options diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index 07648c644..aa5b3fd11 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -17,7 +17,7 @@ catalogue.gui.update_available=Update (%s): %s catalogue.gui.update_available_no_page=Update (%s) catalogue.gui.beta_update_available=Beta Update (%s): %s catalogue.gui.beta_update_available_no_page=Beta Update (%s) -catalogue.gui.info=This menu is provided by Catalogue. Click here to open the CurseForge page for this mod! +#catalogue.gui.info=This menu is provided by Catalogue. Click here to open the CurseForge page for this mod! catalogue.gui.favourite=Mark as Favorite catalogue.gui.remove_favourite=Remove as Favorite catalogue.gui.options=Options diff --git a/src/main/resources/assets/catalogue/lang/pl_pl.lang b/src/main/resources/assets/catalogue/lang/pl_pl.lang index 683ffbe39..26564e701 100644 --- a/src/main/resources/assets/catalogue/lang/pl_pl.lang +++ b/src/main/resources/assets/catalogue/lang/pl_pl.lang @@ -1,2 +1,2 @@ catalogue.gui.no_selection=Brak wybranego moda... -catalogue.gui.info=To menu zostało przeprojektowane przez moda Catalogue. Kliknij tutaj, aby otworzyć stronę tego moda w serwisie CurseForge! +#catalogue.gui.info=To menu zostało przeprojektowane przez moda Catalogue. Kliknij tutaj, aby otworzyć stronę tego moda w serwisie CurseForge! diff --git a/src/main/resources/assets/catalogue/lang/ru_ru.lang b/src/main/resources/assets/catalogue/lang/ru_ru.lang index 43a759b8b..1f0dc475c 100644 --- a/src/main/resources/assets/catalogue/lang/ru_ru.lang +++ b/src/main/resources/assets/catalogue/lang/ru_ru.lang @@ -11,7 +11,7 @@ catalogue.gui.contributors=Участник(ы): %s catalogue.gui.credits=Приписание: %s catalogue.gui.version=Версия: %s catalogue.gui.update_available=Обновление: %s -catalogue.gui.info=Это меню предоставлено Catalogue. Нажмите, чтобы открыть страницу CurseForge для этого мода! +#catalogue.gui.info=Это меню предоставлено Catalogue. Нажмите, чтобы открыть страницу CurseForge для этого мода! catalogue.gui.favourite=Отметить как избранное catalogue.gui.remove_favourite=Удалить из избранного catalogue.gui.options=Настройки diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index f164e0650..2583fbc48 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -17,7 +17,7 @@ catalogue.gui.update_available=更新(%s):%s catalogue.gui.update_available_no_page=更新(%s) catalogue.gui.beta_update_available=Beta更新(%s):%s catalogue.gui.beta_update_available_no_page=Beta更新(%s) -catalogue.gui.info=此菜单由Catalogue提供。单击此处打开此模组的CurseForge页面! +#catalogue.gui.info=此菜单由Catalogue提供。单击此处打开此模组的CurseForge页面! catalogue.gui.favourite=标记为收藏 catalogue.gui.remove_favourite=取消收藏 catalogue.gui.options=选项 From 14459bf3e07fe8dacc919119be519748029e0829 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:37:18 +0800 Subject: [PATCH 86/91] Use customProperties if metadata keys not exist --- .../catalogue/client/CleanroomModData.java | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index ce28a9fb9..02eba1887 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -78,19 +78,22 @@ public String getDescription() { @Nullable @Override public String getItemIcon() { - return this.metadata != null ? this.metadata.iconItem : null; + return this.metadata != null ? (this.metadata.iconItem.isBlank() ? + this.info.getCustomModProperties().get("iconItem") : null) : null; } @Nullable @Override public String getImageIcon() { - return this.metadata != null ? this.metadata.iconFile : null; + return this.metadata != null ? (this.metadata.iconFile.isBlank() ? + this.info.getCustomModProperties().get("iconFile") : null) : null; } @Nullable @Override public String getLicense() { - return this.metadata != null ? this.metadata.license : null; + return this.metadata != null ? (this.metadata.license.isBlank() ? + this.info.getCustomModProperties().get("license") : null) : null; } @Nullable @@ -114,7 +117,8 @@ public String getHomepage() { @Nullable @Override public String getIssueTracker() { - return this.metadata != null ? this.metadata.issueTrackerUrl : null; + return this.metadata != null ? (this.metadata.issueTrackerUrl.isBlank() ? + this.info.getCustomModProperties().get("issueTrackerUrl") : null) : null; } @Nullable @@ -126,7 +130,8 @@ public String getBanner() { @Nullable @Override public String getBackground() { - return this.metadata != null ? this.metadata.backgroundFile : null; + return this.metadata != null ? (this.metadata.backgroundFile.isBlank() ? + this.info.getCustomModProperties().get("backgroundFile") : null) : null; } @Nullable @@ -194,25 +199,25 @@ public void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y) { public String getUpdateText(Update update) { ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.info); if (result == null) return null; - switch (result.status) { - case BETA: - return TextFormatting.GOLD + I18n.format("catalogue.gui.beta"); - case AHEAD: - return TextFormatting.LIGHT_PURPLE + I18n.format("catalogue.gui.ahead", update.latestFound()); - case BETA_OUTDATED: + return switch (result.status) { + case BETA -> TextFormatting.GOLD + I18n.format("catalogue.gui.beta"); + case AHEAD -> TextFormatting.LIGHT_PURPLE + I18n.format("catalogue.gui.ahead", update.latestFound()); + case BETA_OUTDATED -> { if (update.homepage() != null && !update.homepage().isBlank()) { - return TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available", update.latestFound(), update.homepage()); + yield TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available", update.latestFound(), update.homepage()); } else { - return TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available_no_page", update.latestFound()); + yield TextFormatting.GOLD + I18n.format("catalogue.gui.beta_update_available_no_page", update.latestFound()); } - case OUTDATED: + } + case OUTDATED -> { if (update.homepage() != null && !update.homepage().isBlank()) { - return TextFormatting.GREEN + I18n.format("catalogue.gui.update_available", update.latestFound(), update.homepage()); + yield TextFormatting.GREEN + I18n.format("catalogue.gui.update_available", update.latestFound(), update.homepage()); } else { - return TextFormatting.GREEN + I18n.format("catalogue.gui.update_available_no_page", update.latestFound()); + yield TextFormatting.GREEN + I18n.format("catalogue.gui.update_available_no_page", update.latestFound()); } - } - return null; + } + default -> null; + }; } @Nullable From 11e7c3c2f06fbc358e1dc9293c79f9e1c08a0d1b Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:37:58 +0800 Subject: [PATCH 87/91] Fix lang key --- src/main/resources/assets/catalogue/lang/en_au.lang | 4 ++-- src/main/resources/assets/catalogue/lang/en_us.lang | 4 ++-- src/main/resources/assets/catalogue/lang/zh_cn.lang | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/assets/catalogue/lang/en_au.lang b/src/main/resources/assets/catalogue/lang/en_au.lang index 7088bf07e..c3f330756 100644 --- a/src/main/resources/assets/catalogue/lang/en_au.lang +++ b/src/main/resources/assets/catalogue/lang/en_au.lang @@ -44,8 +44,8 @@ catalogue.gui.website=Website catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s -catalogue.config.enable_mod=Enable Mod -catalogue.config.enable_mod.tooltip=Whether enable Catalogue mod. \nSetting it false will stop Catalogue redirecting Forge's mod list calls. +catalogue.config.enable=Enable Mod +catalogue.config.enable.tooltip=Whether enable Catalogue mod. \nSetting it false will stop Catalogue redirecting Forge's mod list calls. catalogue.config.library_list=Library List catalogue.config.library_list.tooltip=The list of library mods' mod ids.\nThey will have grey names in the mod list. catalogue.config.ignored_dependencies_list=Ignored Dependencies List diff --git a/src/main/resources/assets/catalogue/lang/en_us.lang b/src/main/resources/assets/catalogue/lang/en_us.lang index aa5b3fd11..dc984938f 100644 --- a/src/main/resources/assets/catalogue/lang/en_us.lang +++ b/src/main/resources/assets/catalogue/lang/en_us.lang @@ -44,8 +44,8 @@ catalogue.gui.website=Website catalogue.gui.submit_bug=Submit Bug catalogue.gui.mod_id=Mod ID: %s -catalogue.config.enable_mod=Enable -catalogue.config.enable_mod.tooltip=Whether enable Catalogue. \nSetting it false will stop Catalogue redirecting Forge's mod list calls. +catalogue.config.enable=Enable +catalogue.config.enable.tooltip=Whether enable Catalogue. \nSetting it false will stop Catalogue redirecting Forge's mod list calls. catalogue.config.library_list=Library List catalogue.config.library_list.tooltip=The list of library mods' mod ids.\nThey will have grey names in the mod list. catalogue.config.ignored_dependencies_list=Ignored Dependencies List diff --git a/src/main/resources/assets/catalogue/lang/zh_cn.lang b/src/main/resources/assets/catalogue/lang/zh_cn.lang index 2583fbc48..839603b9f 100644 --- a/src/main/resources/assets/catalogue/lang/zh_cn.lang +++ b/src/main/resources/assets/catalogue/lang/zh_cn.lang @@ -44,8 +44,8 @@ catalogue.gui.website=网站 catalogue.gui.submit_bug=提交Bug catalogue.gui.mod_id=Mod ID:%s -catalogue.config.enable_mod=启用 -catalogue.config.enable_mod.tooltip=控制是否启用Catalogue。 \n设置为否会阻止Catalogue重定向Forge模组列表的调用。 +catalogue.config.enable=启用 +catalogue.config.enable.tooltip=控制是否启用Catalogue。 \n设置为否会阻止Catalogue重定向Forge模组列表的调用。 catalogue.config.library_list=库列表 catalogue.config.library_list.tooltip=库模组的Mod ID列表。\n它们会在模组列表中显示灰色名字。 catalogue.config.ignored_dependencies_list=忽略前置列表 From 68ab65ca25ba1d61fdc4acb073f8c5aee9f325cc Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sun, 15 Feb 2026 14:01:53 +0800 Subject: [PATCH 88/91] Fix logic --- .../catalogue/client/CleanroomModData.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index 02eba1887..9915dba61 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -78,22 +78,22 @@ public String getDescription() { @Nullable @Override public String getItemIcon() { - return this.metadata != null ? (this.metadata.iconItem.isBlank() ? - this.info.getCustomModProperties().get("iconItem") : null) : null; + return this.metadata != null && !this.metadata.iconItem.isBlank() ? + this.metadata.iconItem : this.info.getCustomModProperties().get("iconItem"); } @Nullable @Override public String getImageIcon() { - return this.metadata != null ? (this.metadata.iconFile.isBlank() ? - this.info.getCustomModProperties().get("iconFile") : null) : null; + return this.metadata != null && !this.metadata.iconFile.isBlank() ? + this.metadata.iconFile : this.info.getCustomModProperties().get("iconFile"); } @Nullable @Override public String getLicense() { - return this.metadata != null ? (this.metadata.license.isBlank() ? - this.info.getCustomModProperties().get("license") : null) : null; + return this.metadata != null && !this.metadata.license.isBlank() ? + this.metadata.license : this.info.getCustomModProperties().get("license"); } @Nullable @@ -117,8 +117,8 @@ public String getHomepage() { @Nullable @Override public String getIssueTracker() { - return this.metadata != null ? (this.metadata.issueTrackerUrl.isBlank() ? - this.info.getCustomModProperties().get("issueTrackerUrl") : null) : null; + return this.metadata != null && !this.metadata.issueTrackerUrl.isBlank() ? + this.metadata.issueTrackerUrl : this.info.getCustomModProperties().get("issueTrackerUrl"); } @Nullable @@ -130,8 +130,8 @@ public String getBanner() { @Nullable @Override public String getBackground() { - return this.metadata != null ? (this.metadata.backgroundFile.isBlank() ? - this.info.getCustomModProperties().get("backgroundFile") : null) : null; + return this.metadata != null && !this.metadata.backgroundFile.isBlank() ? + this.metadata.backgroundFile : this.info.getCustomModProperties().get("backgroundFile"); } @Nullable From 0ce9a789e9fd5b94dc95733506c444a6f036435d Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:33:03 +0800 Subject: [PATCH 89/91] Fix color pollution --- .../catalogue/client/screen/widget/CatalogueIconButton.java | 2 +- .../catalogue/client/screen/widget/CatalogueTextButton.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java index 55e6ba720..d1caea567 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueIconButton.java @@ -41,7 +41,6 @@ public void drawButton(@NotNull Minecraft minecraft, int mouseX, int mouseY, flo if (this.visible) { FontRenderer fontrenderer = minecraft.fontRenderer; minecraft.getTextureManager().bindTexture(TEXTURE); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); int contentWidth = 10 + fontrenderer.getStringWidth(this.label) + (!this.label.isEmpty() ? 4 : 0); int iconX = this.x + (this.width - contentWidth) / 2; int iconY = this.y + (this.height - 10) / 2; @@ -50,6 +49,7 @@ public void drawButton(@NotNull Minecraft minecraft, int mouseX, int mouseY, flo drawModalRectWithCustomSizedTexture(iconX, iconY, this.u, this.v, 10, 10, 64, 64); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); this.drawString(fontrenderer, this.label, iconX + 14, iconY + 1, this.getFGColor()); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); } } } diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java index 1f9ef0aac..5298c65e2 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/widget/CatalogueTextButton.java @@ -57,10 +57,12 @@ public void renderScrollingString(FontRenderer font, String text, int centerX, i double d3 = Utils.lerp(d2, 0.0F, l); ClientHelper.scissor(minX, minY, maxX - minX, maxY - minY); this.drawString(font, text, minX - (int) d3, j, color); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GL11.glDisable(GL11.GL_SCISSOR_TEST); } else { int i1 = MathHelper.clamp(centerX, minX + i / 2, maxX - i / 2); this.drawCenteredString(font, text, i1, j, color); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); } } From 97e326e49175a600274607feb66e7e5e46f0df0d Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Mon, 2 Mar 2026 03:32:27 +0800 Subject: [PATCH 90/91] Fix repeat event --- .../catalogue/client/screen/CatalogueModListScreen.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java index bc4efc952..022f55156 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java +++ b/src/main/java/com/cleanroommc/catalogue/client/screen/CatalogueModListScreen.java @@ -120,7 +120,6 @@ public class CatalogueModListScreen extends GuiScreen implements DropdownMenuHan * Time record of text box clicking. */ private long lastClickTime; - private boolean didRepeatEvents; public CatalogueModListScreen(GuiScreen parent) { super(); @@ -145,7 +144,6 @@ public void setMenu(@Nullable DropdownMenu menu) { @Override public void initGui() { - this.didRepeatEvents = Keyboard.areRepeatEventsEnabled(); Keyboard.enableRepeatEvents(true); this.searchTextField = new CatalogueTextField(0, this.fontRenderer, 11, 25, 148, 20) { @Override @@ -204,7 +202,7 @@ public int getWidth() { @Override public void onGuiClosed() { - Keyboard.enableRepeatEvents(this.didRepeatEvents); + Keyboard.enableRepeatEvents(false); FAVOURITES.save(); } From ba44631f2d915340f0772d8796b46215dc530ddd Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Sat, 4 Apr 2026 19:22:08 +0800 Subject: [PATCH 91/91] Carbon Config compat --- .../catalogue/client/CleanroomModData.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java index 9915dba61..e5139b308 100644 --- a/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java +++ b/src/main/java/com/cleanroommc/catalogue/client/CleanroomModData.java @@ -19,6 +19,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.lang.reflect.Method; import java.util.*; import java.util.stream.Collectors; @@ -170,6 +171,7 @@ public Set getChildMods() { @Override public boolean hasConfig() { + ensureCarbonConfigsRegistered(); IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(this.info); if (guiFactory == null) return false; return guiFactory.hasConfigGui(); @@ -186,6 +188,20 @@ public void openConfigScreen(Minecraft minecraft, GuiScreen parent) { } } + private static boolean carbonConfigRegistrationAttempted; + + private static void ensureCarbonConfigsRegistered() { + if (carbonConfigRegistrationAttempted) return; + carbonConfigRegistrationAttempted = true; + try { + Class cls = Class.forName("carbonconfiglib.impl.internal.EventHandler"); + Method m = cls.getDeclaredMethod("registerConfigs"); + m.setAccessible(true); + m.invoke(cls.getField("INSTANCE").get(null)); + } catch (ReflectiveOperationException | LinkageError ignored) { + } + } + @Override public void drawUpdateIcon(Minecraft minecraft, Update update, int x, int y) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);

L;CG{hO%`51Db;H=ldmG#A?yfd5>;BLVT?m;#$ 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 IeRucgcNbFd;s5{u literal 0 HcmV?d00001 From 1648a0bc39949f0557b1d570b230cc4d84e20cb0 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:56:20 +0800 Subject: [PATCH 10/91] Update forge and mcp logo, add logoBlur and iconBlur --- .../screen/CatalogueModListScreen.java | 7 +++---- .../fml/common/ModMetadata.java | 4 +++- src/main/resources/forge_logo.png | Bin 1112 -> 11237 bytes src/main/resources/mcplogo.png | Bin 3926 -> 18770 bytes 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index e3d55e93b..c676fe8f7 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -300,7 +300,6 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, drawString(fontRenderer, this.getFormattedModName(), left + 24, top + 2, 0xFFFFFF); drawString(fontRenderer, TextFormatting.GRAY + this.info.getDisplayVersion(), left + 24, top + 12, 0xFFFFFF); - // WIP CatalogueModListScreen.this.loadAndCacheIcon(this.info); // Draw icon @@ -759,7 +758,7 @@ private void loadAndCacheLogo(ModContainer info) { } if (logo == null) return; TextureManager textureManager = this.mc.getTextureManager(); - LOGO_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("modlogo", this.createLogoTexture(logo, true)), new Size2i(logo.getWidth(), logo.getHeight()))); + LOGO_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("modlogo", this.createLogoTexture(logo, metadata.logoBlur)), new Size2i(logo.getWidth(), logo.getHeight()))); } catch (IOException ignored) { } } @@ -784,7 +783,7 @@ private void loadAndCacheIcon(ModContainer info) { if (is != null) icon = TextureUtil.readBufferedImage(is); if (icon != null) { TextureManager textureManager = this.mc.getTextureManager(); - ICON_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(icon, false)), new Size2i(icon.getWidth(), icon.getHeight()))); + ICON_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(icon, metadata.iconBlur)), new Size2i(icon.getWidth(), icon.getHeight()))); return; } } catch (IOException ignored) { @@ -820,7 +819,7 @@ private void loadAndCacheIcon(ModContainer info) { } /* Since the icon will be same as the logo, we can cache into both icon and logo cache */ - DynamicTexture texture = this.createLogoTexture(logo, true); + DynamicTexture texture = this.createLogoTexture(logo, metadata.logoBlur); Size2i size = new Size2i(logo.getWidth(), logo.getHeight()); ResourceLocation textureId = textureManager.getDynamicTextureLocation("catalogueicon", texture); ICON_CACHE.put(modId, Pair.of(textureId, size)); diff --git a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java index d6b842dd3..780e5af58 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java +++ b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java @@ -50,8 +50,10 @@ public class ModMetadata public String issueTrackerUrl = ""; public String logoFile = ""; + public boolean logoBlur = true; // Blur the border to make it smooth. True by default. public String iconFile = ""; - public String iconItem = ""; + public boolean iconBlur = true; + public String iconItem = ""; // Customized item or block as icon. Will not applied when iconFile is valid. public String version = ""; public List authorList = Lists.newArrayList(); public String credits = ""; diff --git a/src/main/resources/forge_logo.png b/src/main/resources/forge_logo.png index 7129008d1fd5910071a73571c2c1b12a97061416..49db5c8a777afb7ea916dc10997b58296097878c 100644 GIT binary patch literal 11237 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 D>F!Ab literal 1112 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 Date: Tue, 26 Aug 2025 20:24:51 +0800 Subject: [PATCH 11/91] Background https://github.com/MrCrayfish/Catalogue/commit/5397ccf1505eff16012c6e66ac1df490ab983a52 --- .../screen/CatalogueModListScreen.java | 54 +++++++++++++++++++ .../fml/common/ModMetadata.java | 1 + 2 files changed, 55 insertions(+) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index c676fe8f7..0258a8a34 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -4,12 +4,15 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.IResourcePack; import net.minecraft.init.Blocks; @@ -51,6 +54,7 @@ public class CatalogueModListScreen extends GuiScreen { private static final Map> LOGO_CACHE = new HashMap<>(); private static final Map> ICON_CACHE = new HashMap<>(); private static final Map ITEM_CACHE = new HashMap<>(); + private static ResourceLocation cachedBackground; private CatalogueTextField searchTextField; private ModList modList; @@ -601,6 +605,8 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { int contentWidth = this.width - contentLeft - 10; if (this.selectedModInfo != null) { + this.drawBackground(this.width - contentLeft + 10, this.modList.right + 12, 0); + // Draw mod logo this.drawLogo(contentWidth, contentLeft, 10, this.width - (this.modList.right + 12 + 10) - 10, 50); @@ -830,6 +836,33 @@ private void loadAndCacheIcon(ModContainer info) { } } + private void loadAndCacheBackground(ModContainer info) { + // Deletes the last cached background since they are large images + if (cachedBackground != null) { + TextureManager textureManager = this.mc.getTextureManager(); + textureManager.deleteTexture(cachedBackground); + } + cachedBackground = null; + + ModMetadata metadata = info.getMetadata(); + if (metadata == null) return; + String s = metadata.backgroundFile; + if(s.isEmpty()) return; + + if (!s.startsWith("/")) { + s = "/" + s; + } + + BufferedImage image = null; + try(InputStream is = getClass().getResourceAsStream(s)) { + if (is != null) image = TextureUtil.readBufferedImage(is); + if (image == null || image.getWidth() != 512 || image.getHeight() != 256) return; + TextureManager textureManager = this.mc.getTextureManager(); + cachedBackground = textureManager.getDynamicTextureLocation("cataloguebackground", this.createLogoTexture(image, false)); + } catch(IOException ignored) { + } + } + private DynamicTexture createLogoTexture(BufferedImage image, boolean smooth) { return new DynamicTexture(image) { @Override @@ -839,6 +872,26 @@ public void updateDynamicTexture() { }; } + private void drawBackground(int contentWidth, int x, int y) { + if(this.selectedModInfo == null) return; + if(cachedBackground == null) return; + + this.mc.renderEngine.bindTexture(cachedBackground); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder builder = tessellator.getBuffer(); + builder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + builder.pos(x, y, this.zLevel).tex(0, 0).color(1.0F, 1.0F, 1.0F, 1.0F).endVertex(); + builder.pos(x, y + 128, this.zLevel).tex(0, 1).color(0.0F, 0.0F, 0.0F, 0.0F).endVertex(); + builder.pos(x + contentWidth, y + 128, this.zLevel).tex(1, 1).color(0.0F, 0.0F, 0.0F, 0.0F).endVertex(); + builder.pos(x + contentWidth, y, this.zLevel).tex(1, 0).color(1.0F, 1.0F, 1.0F, 1.0F).endVertex(); + tessellator.draw(); + + GlStateManager.disableBlend(); + } + @SuppressWarnings("SameParameterValue") private void drawLogo(int contentWidth, int x, int y, int maxWidth, int maxHeight) { if (this.selectedModInfo != null) { @@ -885,6 +938,7 @@ private void setActiveTooltip(String content) { private void setSelectedModInfo(ModContainer selectedModInfo) { this.selectedModInfo = selectedModInfo; this.loadAndCacheLogo(selectedModInfo); + this.loadAndCacheBackground(selectedModInfo); this.configButton.visible = true; this.websiteButton.visible = true; this.issueButton.visible = true; diff --git a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java index 780e5af58..be9a4921b 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java +++ b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java @@ -54,6 +54,7 @@ public class ModMetadata public String iconFile = ""; public boolean iconBlur = true; public String iconItem = ""; // Customized item or block as icon. Will not applied when iconFile is valid. + public String backgroundFile = ""; public String version = ""; public List authorList = Lists.newArrayList(); public String credits = ""; From 5d64fe00fff8c90aca30d6fdc0484660ad5f67e8 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 20:37:42 +0800 Subject: [PATCH 12/91] Add a dummy mod. TEST ONLY. --- .../common/CatalogueContainer.java | 30 ++++++++++++++++++ .../net/minecraftforge/fml/common/Loader.java | 2 ++ src/main/resources/catalogue_background.png | Bin 0 -> 123276 bytes src/main/resources/catalogue_icon.png | Bin 0 -> 2635 bytes 4 files changed, 32 insertions(+) create mode 100644 src/main/java/com/cleanroommc/common/CatalogueContainer.java create mode 100644 src/main/resources/catalogue_background.png create mode 100644 src/main/resources/catalogue_icon.png diff --git a/src/main/java/com/cleanroommc/common/CatalogueContainer.java b/src/main/java/com/cleanroommc/common/CatalogueContainer.java new file mode 100644 index 000000000..0a0a12b52 --- /dev/null +++ b/src/main/java/com/cleanroommc/common/CatalogueContainer.java @@ -0,0 +1,30 @@ +package com.cleanroommc.common; + +import com.google.common.eventbus.EventBus; +import net.minecraftforge.fml.common.DummyModContainer; +import net.minecraftforge.fml.common.LoadController; +import net.minecraftforge.fml.common.ModMetadata; + +import java.util.Arrays; + +public class CatalogueContainer extends DummyModContainer { + public CatalogueContainer() { + super(new ModMetadata()); + ModMetadata meta = this.getMetadata(); + meta.modId = "catalogue"; + meta.name = "Catalogue"; + meta.description = "Updates Forge's mod list with a new and improved design"; + meta.version = "1.0.0"; + meta.authorList = Arrays.asList("MrCrayfish", "RuiXuqi"); + meta.logoFile = "/catalogue_icon.png"; + meta.logoBlur = false; + meta.iconFile = "/catalogue_icon.png"; + meta.iconBlur = false; + meta.backgroundFile = "/catalogue_background.png"; + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + return true; + } +} diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index ad451e086..237230fed 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -38,6 +38,7 @@ import java.util.Properties; import java.util.Set; +import com.cleanroommc.common.CatalogueContainer; import com.cleanroommc.common.CleanroomContainer; import com.cleanroommc.common.MixinContainer; import com.cleanroommc.common.ConfigAnytimeContainer; @@ -378,6 +379,7 @@ private ModDiscoverer identifyMods(List additionalContainers) mods.add(new InjectedModContainer(new CleanroomContainer(), FMLSanityChecker.fmlLocation)); mods.add(new InjectedModContainer(new MixinContainer(), FMLSanityChecker.fmlLocation)); mods.add(new InjectedModContainer(new ConfigAnytimeContainer(), FMLSanityChecker.fmlLocation)); + mods.add(new InjectedModContainer(new CatalogueContainer(), FMLSanityChecker.fmlLocation)); for (String cont : injectedContainers) { diff --git a/src/main/resources/catalogue_background.png b/src/main/resources/catalogue_background.png new file mode 100644 index 0000000000000000000000000000000000000000..a14604c70a77f45fac5462050a6cc118926942d8 GIT binary patch literal 123276 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 pXg_{i+Z|}uWgYV4hji{a=g!`onLFKk zXJ^(579lVUtRGpJf`(xbX=Owpq)8BlmWr@`ELe2ze7UZ>+afe@&wK8< z?|I(m{W@n}80ha^vS{@phGCYZ`jUfG9txkfyXd&*d=^l#u-LcNXPCvy!zaS*Kd_2n z+Bhe(CD@XF+(d3Z3bAXEXeD2y+6=Rd;2MJh0*Km zIej}lvT0|32JPH|49s<|XV+Cs3XmrOWGnex!8a>$u8M2YdAQ7TY*i)L5$C!?gY1^{ z0Gn_IBu8Kze1e5VEWGb2U94-(%kd5H83 zlR~hjVXQdh2ChHk&cLnt`4)y4H4PR^UalG*jChhGd5Y~*U9rlqXeK-Y1J}#AZmyB2 zfrdpkkq8rGun%o^3fL|C4~L}cdq@%nB+f~KBmzMJf|L<8QxwdY_9z`?p{AOqrGQ}o zCn<{}5cN=55=~jWMcKuUJu(;7blOZ6`~VgZNhRYPB^7lXY}yzbM6q>%F%*EIDI(Af zfesQTgh(p1WVOnR?oYa?6b2*QU#nc~B8t%rsA)qL+Ss-zU0noNQY>HyN(>-kX|Wh0 zatvt=w2huan+N75qlT<7MKmlShL9}-NGLT)QB+{)$O4oMf;JIk0~#EA({94exnA1E zG`jNS^;9A;;JLPwqaXai-VJQ3C!valssbr0ax6xs?Rt4gYt6~SEa8iVY*_h}26ea3 z@oAbz>bCFs#>ecRiw?PHk8 z`BbtyQ~B=K@84T^rfbFMmF4$#9XP%HlVu;A2o{{3x9gi@m+#xRPdx@--`;-rrrwru zW#jJNkB`5y=E`rsjyvoc5&g$?9s2@`1`4q+d9khf8}I-0+uEsEb_jIUX5qp= z_n+Q=HL~ZYx4xbj-FxxP3$4$dymV;_a&=pMcc*ndvX@+2@Y(pVc5Up`QGfm}W*OVo z9=Tl{S$eMZ#nnq&w|2ZcF$?0UbFH5)X|k)EX^N{IhbBhvkvl@>&Ba^HU4C_9^rOfO z9NJ#GF<gi7&+wkPBe*rSRIO6~S literal 0 HcmV?d00001 From 595b361638cd815e9dca1cbc5519590eadefce2b Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 20:46:47 +0800 Subject: [PATCH 13/91] Update CatalogueContainer.java --- src/main/java/com/cleanroommc/common/CatalogueContainer.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/cleanroommc/common/CatalogueContainer.java b/src/main/java/com/cleanroommc/common/CatalogueContainer.java index 0a0a12b52..84f41182c 100644 --- a/src/main/java/com/cleanroommc/common/CatalogueContainer.java +++ b/src/main/java/com/cleanroommc/common/CatalogueContainer.java @@ -16,10 +16,15 @@ public CatalogueContainer() { meta.description = "Updates Forge's mod list with a new and improved design"; meta.version = "1.0.0"; meta.authorList = Arrays.asList("MrCrayfish", "RuiXuqi"); + meta.url = "https://github.com/RuiXuqi/Catalogue-Vintage"; + meta.issueTrackerUrl = "https://github.com/RuiXuqi/Catalogue-Vintage/issues"; + meta.credits = "Hatsondogs for creating icons"; + meta.license = "MIT"; meta.logoFile = "/catalogue_icon.png"; meta.logoBlur = false; meta.iconFile = "/catalogue_icon.png"; meta.iconBlur = false; + meta.iconItem = "minecraft:diamond_sword"; meta.backgroundFile = "/catalogue_background.png"; } From 965d08213f45b0030a32d78ccb26fabe9ced07be Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:43:53 +0800 Subject: [PATCH 14/91] The Great Rename So bad. But it will help back port works https://github.com/MrCrayfish/Catalogue/commit/4f716274a198801f589668f2d9344736afff459b --- .../{ScreenUtil.java => ClientHelper.java} | 14 +- .../screen/CatalogueModListScreen.java | 372 +++++++++--------- 2 files changed, 184 insertions(+), 202 deletions(-) rename src/main/java/net/minecraftforge/fml/client/TEMPmodlist/{ScreenUtil.java => ClientHelper.java} (81%) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ClientHelper.java similarity index 81% rename from src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java rename to src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ClientHelper.java index 07a710dd4..b1e36b6aa 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ScreenUtil.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/ClientHelper.java @@ -3,15 +3,12 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.ScaledResolution; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import org.lwjgl.opengl.GL11; /** * Author: MrCrayfish */ -public class ScreenUtil { +public class ClientHelper { /** * Creates a scissor test using minecraft screen coordinates instead of pixel coordinates. * @param screenX @@ -37,15 +34,6 @@ public static boolean isMouseWithin(int x, int y, int width, int height, int mou return mouseX >= x && mouseX < x + width && mouseY >= y && mouseY < y + height; } - public static class Size2i { - public final int width, height; - - public Size2i(int width, int height) { - this.width = width; - this.height = height; - } - } - public static void blit(int x, int y, int width, int height, float uOffset, float vOffset, int uWidth, int vHeight, int textureWidth, int textureHeight) { Gui.drawScaledCustomSizeModalRect(x, y, uOffset, vOffset, uWidth, vHeight, width, height, textureWidth, textureHeight); } diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 0258a8a34..3e0832cf9 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -26,8 +26,7 @@ import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.client.IModGuiFactory; -import net.minecraftforge.fml.client.TEMPmodlist.ScreenUtil; -import net.minecraftforge.fml.client.TEMPmodlist.ScreenUtil.Size2i; +import net.minecraftforge.fml.client.TEMPmodlist.ClientHelper; import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueCheckBoxButton; import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueIconButton; import net.minecraftforge.fml.client.TEMPmodlist.screen.widget.CatalogueListExtended; @@ -42,6 +41,7 @@ import javax.annotation.Nullable; import java.awt.Desktop; +import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; @@ -51,14 +51,17 @@ public class CatalogueModListScreen extends GuiScreen { private static final ResourceLocation MISSING_BANNER = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/missing_banner.png"); private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); - private static final Map> LOGO_CACHE = new HashMap<>(); - private static final Map> ICON_CACHE = new HashMap<>(); - private static final Map ITEM_CACHE = new HashMap<>(); + private static final Map> BANNER_CACHE = new HashMap<>(); + private static final Map> IMAGE_ICON_CACHE = new HashMap<>(); + private static final Map ITEM_ICON_CACHE = new HashMap<>(); + private static final Map CACHED_MODS = new HashMap<>(); private static ResourceLocation cachedBackground; + private static boolean loaded = false; + private final GuiScreen parentScreen; private CatalogueTextField searchTextField; private ModList modList; - private ModContainer selectedModInfo; + private ModContainer selectedModData; private CatalogueIconButton modFolderButton; private CatalogueIconButton configButton; private CatalogueIconButton websiteButton; @@ -70,14 +73,14 @@ public class CatalogueModListScreen extends GuiScreen { private StringList descriptionList; private long lastClickTime; - private GuiScreen mainMenu; - /** - * @param mainMenu - */ - public CatalogueModListScreen(GuiScreen mainMenu) { + public CatalogueModListScreen(GuiScreen parent) { super(); - this.mainMenu = mainMenu; + this.parentScreen = parent; + if(!loaded) { + Loader.instance().getActiveModList().forEach(data -> CACHED_MODS.put(data.getModId(), data)); + loaded = true; + } } @Override @@ -117,10 +120,10 @@ public void initGui() { this.modList.filterAndUpdateList(this.searchTextField.getText()); // Resizing window causes all widgets to be recreated, therefore need to update selected info - if (this.selectedModInfo != null) { - this.setSelectedModInfo(this.selectedModInfo); + if (this.selectedModData != null) { + this.setSelectedModData(this.selectedModData); this.updateSelectedModList(); - ModEntry entry = this.modList.getEntryFromInfo(this.selectedModInfo); + ModListEntry entry = this.modList.getEntryFromInfo(this.selectedModData); if (entry != null) { this.modList.centerScrollOn(entry); } @@ -132,7 +135,7 @@ public void initGui() { public void actionPerformed(GuiButton button) { switch (button.id) { case 1: - mc.displayGuiScreen(mainMenu); + mc.displayGuiScreen(parentScreen); break; case 2: try { @@ -143,18 +146,18 @@ public void actionPerformed(GuiButton button) { break; case 3: try { - IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedModInfo); + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedModData); GuiScreen newScreen = guiFactory.createConfigGui(this); this.mc.displayGuiScreen(newScreen); } catch (Exception e) { - FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", selectedModInfo.getModId(), e); + FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", selectedModData.getModId(), e); } break; case 4: - this.openLink(0, this.selectedModInfo); + this.openLink(0, this.selectedModData); break; case 5: - this.openLink(1, this.selectedModInfo); + this.openLink(1, this.selectedModData); break; case 6: this.modList.filterAndUpdateList(this.searchTextField.getText()); @@ -171,17 +174,17 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawModInfo(mouseX, mouseY, partialTicks); super.drawScreen(mouseX, mouseY, partialTicks); - ModContainer info = Loader.instance().getIndexedModList().get("catalogue"); - if (info != null) this.loadAndCacheLogo(info); - Pair pair = LOGO_CACHE.get("catalogue"); + Optional optional = Optional.ofNullable(CACHED_MODS.get("catalogue")); + optional.ifPresent(this::loadAndCacheLogo); + Pair pair = BANNER_CACHE.get("catalogue"); if (pair != null && pair.getLeft() != null) { ResourceLocation textureId = pair.getLeft(); - Size2i size = pair.getRight(); + Dimension size = pair.getRight(); mc.getTextureManager().bindTexture(textureId); - ScreenUtil.blit(10, 9, 10, 10, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + ClientHelper.blit(10, 9, 10, 10, 0.0F, 0.0F, size.width, size.height, size.width, size.height); } - if (ScreenUtil.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { + if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY)) { this.setActiveTooltip(I18n.format("fml.menu.mods.info")); this.tooltipYOffset = 10; } @@ -209,7 +212,7 @@ protected void keyTyped(char typedChar, int key) throws IOException { } private class ModList extends CatalogueListExtended { - private List entries = Lists.newArrayList(); + private List entries = Lists.newArrayList(); private int selectedIndex = -1; public ModList() { @@ -218,7 +221,7 @@ public ModList() { @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { - ScreenUtil.scissor(this.left, this.top, this.width, this.height); + ClientHelper.scissor(this.left, this.top, this.width, this.height); super.drawScreen(mouseX, mouseY, partialTicks); GL11.glDisable(GL11.GL_SCISSOR_TEST); } @@ -226,24 +229,24 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { @Override protected void elementClicked(int slotIndex, boolean isDoubleClick, int mouseX, int mouseY) { selectedIndex = slotIndex; - CatalogueModListScreen.ModEntry entry = entries.get(slotIndex); - CatalogueModListScreen.this.setSelectedModInfo(entry.info); + ModListEntry entry = entries.get(slotIndex); + CatalogueModListScreen.this.setSelectedModData(entry.data); } public void filterAndUpdateList(String text) { - List entries = Loader.instance().getActiveModList().stream() - .filter(info -> info.getName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) - .filter(info -> !updatesButton.selected() || shouldUpdate(ForgeVersion.getCleanResult(info))) - .map(info -> new ModEntry(info, this)) - .sorted(Comparator.comparing(entry -> entry.info.getName())) + List entries = CACHED_MODS.values().stream() + .filter(data -> data.getName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) + .filter(data -> !updatesButton.selected() || shouldUpdate(ForgeVersion.getCleanResult(data))) + .map(data -> new ModListEntry(data, this)) + .sorted(Comparator.comparing(entry -> entry.data.getName())) .collect(Collectors.toList()); this.entries = entries; - this.selectMod(this.getEntryFromInfo(selectedModInfo)); + this.selectMod(this.getEntryFromInfo(selectedModData)); this.setAmountScrolled(0); } - public ModEntry getEntryFromInfo(ModContainer info) { - return this.entries.stream().filter(entry -> entry.info == info).findFirst().orElse(null); + public ModListEntry getEntryFromInfo(ModContainer data) { + return this.entries.stream().filter(entry -> entry.data == data).findFirst().orElse(null); } public void selectMod(int selectedIndex) { @@ -288,12 +291,12 @@ protected int getScrollBarX() { } } - private class ModEntry implements CatalogueListExtended.IGuiListEntry { - private final ModContainer info; + private class ModListEntry implements CatalogueListExtended.IGuiListEntry { + private final ModContainer data; private final ModList list; - public ModEntry(ModContainer info, ModList list) { - this.info = info; + public ModListEntry(ModContainer data, ModList list) { + this.data = data; this.list = list; } @@ -302,16 +305,16 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, left -= 2; // Move 2px, the borders. // Draws mod name and version drawString(fontRenderer, this.getFormattedModName(), left + 24, top + 2, 0xFFFFFF); - drawString(fontRenderer, TextFormatting.GRAY + this.info.getDisplayVersion(), left + 24, top + 12, 0xFFFFFF); + drawString(fontRenderer, TextFormatting.GRAY + this.data.getDisplayVersion(), left + 24, top + 12, 0xFFFFFF); - CatalogueModListScreen.this.loadAndCacheIcon(this.info); + CatalogueModListScreen.this.loadAndCacheIcon(this.data); // Draw icon - if (ICON_CACHE.containsKey(this.info.getModId()) && ICON_CACHE.get(this.info.getModId()).getLeft() != null) { + if (IMAGE_ICON_CACHE.containsKey(this.data.getModId()) && IMAGE_ICON_CACHE.get(this.data.getModId()).getLeft() != null) { ResourceLocation logoResource = TextureMap.LOCATION_MISSING_TEXTURE; - Size2i size = new Size2i(16, 16); + Dimension size = new Dimension(16, 16); - Pair logoInfo = ICON_CACHE.get(this.info.getModId()); + Pair logoInfo = IMAGE_ICON_CACHE.get(this.data.getModId()); if (logoInfo != null && logoInfo.getLeft() != null) { logoResource = logoInfo.getLeft(); size = logoInfo.getRight(); @@ -320,7 +323,7 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, mc.getTextureManager().bindTexture(logoResource); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); - ScreenUtil.blit(left + 4, top + 2, 16, 16, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + ClientHelper.blit(left + 4, top + 2, 16, 16, 0.0F, 0.0F, size.width, size.height, size.width, size.height); GlStateManager.disableBlend(); } else { try { @@ -330,17 +333,17 @@ public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, GlStateManager.disableDepth(); RenderHelper.disableStandardItemLighting(); } catch (Exception e) { - ITEM_CACHE.put(this.info.getModId(), new ItemStack(Blocks.GRASS)); + ITEM_ICON_CACHE.put(this.data.getModId(), new ItemStack(Blocks.GRASS)); } } // Draws an icon if there is an update for the mod - ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.info); - if (shouldDraw(result)) { + ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.data); + if (shouldDraw(update)) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); - int vOffset = result.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; - ScreenUtil.blit(left + rowWidth - 8 - 10, top + 6, result.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); + int vOffset = update.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; + ClientHelper.blit(left + rowWidth - 8 - 10, top + 6, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); } } @@ -351,25 +354,25 @@ public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEven } private ItemStack getItemIcon() { - if (ITEM_CACHE.containsKey(this.info.getModId())) { - return ITEM_CACHE.get(this.info.getModId()); + if (ITEM_ICON_CACHE.containsKey(this.data.getModId())) { + return ITEM_ICON_CACHE.get(this.data.getModId()); } // Put grass as default item icon - ITEM_CACHE.put(this.info.getModId(), new ItemStack(Blocks.GRASS)); + ITEM_ICON_CACHE.put(this.data.getModId(), new ItemStack(Blocks.GRASS)); // Minecraft is a grass block - if (this.info.getModId().equals("minecraft")) return new ItemStack(Blocks.GRASS); + if (this.data.getModId().equals("minecraft")) return new ItemStack(Blocks.GRASS); // Special case for Forge to set item icon to anvil - if (this.info.getModId().equals("forge")) { + if (this.data.getModId().equals("forge")) { ItemStack anvil = new ItemStack(Blocks.ANVIL); - ITEM_CACHE.put("forge", anvil); + ITEM_ICON_CACHE.put("forge", anvil); return anvil; } // Gets the raw item icon resource string - ModMetadata metadata = this.info.getMetadata(); + ModMetadata metadata = this.data.getMetadata(); if (metadata != null && !metadata.autogenerated) { String itemIcon = metadata.iconItem; if (!itemIcon.isEmpty()) { @@ -380,7 +383,7 @@ private ItemStack getItemIcon() { if (item != null) { int meta = parts.length > 2 ? Integer.parseInt(parts[2]) : 0; ItemStack itemStack = new ItemStack(item, 1, meta); - ITEM_CACHE.put(this.info.getModId(), itemStack); + ITEM_ICON_CACHE.put(this.data.getModId(), itemStack); return itemStack; } } catch (Exception ignored) {} @@ -388,18 +391,18 @@ private ItemStack getItemIcon() { } // If the mod doesn't specify an item to use, Catalogue will attempt to get an item from the mod - Optional optional = ForgeRegistries.ITEMS.getValuesCollection().stream().filter(item -> item.getRegistryName().getNamespace().equals(this.info.getModId())).map(ItemStack::new).findFirst(); + Optional optional = ForgeRegistries.ITEMS.getValuesCollection().stream().filter(item -> item.getRegistryName().getNamespace().equals(this.data.getModId())).map(ItemStack::new).findFirst(); if (optional.isPresent()) { ItemStack item = optional.get(); if (!item.isEmpty()) { // If the item is in a creative tab, Catalogue will use the tab's icon if (item.getItem().getCreativeTab() != null) { ItemStack tabItem = item.getItem().getCreativeTab().getIcon(); - if (tabItem != null && !tabItem.isEmpty() && tabItem.getItem().getRegistryName().getNamespace().equals(this.info.getModId())) { + if (tabItem != null && !tabItem.isEmpty() && tabItem.getItem().getRegistryName().getNamespace().equals(this.data.getModId())) { item = tabItem; } } - ITEM_CACHE.put(this.info.getModId(), item); + ITEM_ICON_CACHE.put(this.data.getModId(), item); return item; } } @@ -408,12 +411,12 @@ private ItemStack getItemIcon() { } private String getFormattedModName() { - String name = this.info.getName(); + String name = this.data.getName(); int width = this.list.getListWidth() - (this.list.getMaxScroll() > 0 ? 30 : 24); if (CatalogueModListScreen.this.fontRenderer.getStringWidth(name) > width) { name = CatalogueModListScreen.this.fontRenderer.trimStringToWidth(name, width - 10) + "..."; } - if (this.info.getModId().equals("forge") || this.info.getModId().equals("minecraft") || this.info.getModId().equals("cleanroom")) { + if (this.data.getModId().equals("forge") || this.data.getModId().equals("minecraft") || this.data.getModId().equals("cleanroom")) { return TextFormatting.DARK_GRAY + name; } return name; @@ -436,9 +439,9 @@ public StringList(int width, int height, int left, int top) { this.setSlotXBoundsFromLeft(left); } - public void setTextFromInfo(ModContainer info) { + public void setTextFromInfo(ModContainer data) { this.entries.clear(); - String description = info.getMetadata().description.trim(); + String description = data.getMetadata().description.trim(); List lines = CatalogueModListScreen.this.fontRenderer.listFormattedStringToWidth(description, this.getListWidth()); for (String line : lines) { @@ -449,7 +452,7 @@ public void setTextFromInfo(ModContainer info) { @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { - ScreenUtil.scissor(this.left, this.top, this.width, this.bottom - this.top); + ClientHelper.scissor(this.left, this.top, this.width, this.bottom - this.top); super.drawScreen(mouseX, mouseY, partialTicks); GL11.glDisable(GL11.GL_SCISSOR_TEST); } @@ -512,26 +515,26 @@ public void handleMouseInput() throws IOException { @Override protected void mouseClicked(int mouseX, int mouseY, int button) throws IOException { // Catalogue button - if (ScreenUtil.isMouseWithin(10, 9, 10, 10, mouseX, mouseY) && button == 0) { + if (ClientHelper.isMouseWithin(10, 9, 10, 10, mouseX, mouseY) && button == 0) { this.openLink("https://www.curseforge.com/minecraft/mc-mods/catalogue"); return; } // Version check button - if (this.selectedModInfo != null) { + if (this.selectedModData != null) { int contentLeft = this.modList.right + 12 + 10; - String version = I18n.format("fml.menu.mods.info.version", this.selectedModInfo.getDisplayVersion()); + String version = I18n.format("fml.menu.mods.info.version", this.selectedModData.getDisplayVersion()); int versionWidth = this.fontRenderer.getStringWidth(version); - if (ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { - ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.selectedModInfo); - if (shouldUpdate(result) && result.homepage != null) { - this.openLink(result.homepage); + if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { + ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); + if (shouldUpdate(update) && update.homepage != null) { + this.openLink(update.homepage); } } } // Search Text Field - if (ScreenUtil.isMouseWithin(this.searchTextField.x, this.searchTextField.y, this.searchTextField.width, this.searchTextField.height, mouseX, mouseY)) { + if (ClientHelper.isMouseWithin(this.searchTextField.x, this.searchTextField.y, this.searchTextField.width, this.searchTextField.height, mouseX, mouseY)) { // Right click to empty if (button == 1) { this.searchTextField.setText(""); @@ -576,14 +579,14 @@ private void drawModList(int mouseX, int mouseY, float partialTicks) { GlStateManager.enableBlend(); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); - ScreenUtil.blit(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); + ClientHelper.blit(this.modList.right - 24, 10, 24, 0, 8, 8, 64, 16); GlStateManager.disableBlend(); this.modList.drawScreen(mouseX, mouseY, partialTicks); drawString(this.fontRenderer, TextFormatting.BOLD + I18n.format("fml.menu.mods.title"), 70, 10, 0xFFFFFF); this.searchTextField.drawTextBox(); - if (ScreenUtil.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { + if (ClientHelper.isMouseWithin(this.modList.right - 14, 7, 14, 14, mouseX, mouseY)) { this.setActiveTooltip(I18n.format("fml.menu.mods.filterupdates")); this.tooltipYOffset = 10; } @@ -604,79 +607,69 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { int contentLeft = this.modList.right + 12 + 10; int contentWidth = this.width - contentLeft - 10; - if (this.selectedModInfo != null) { + if (this.selectedModData != null) { this.drawBackground(this.width - contentLeft + 10, this.modList.right + 12, 0); // Draw mod logo - this.drawLogo(contentWidth, contentLeft, 10, this.width - (this.modList.right + 12 + 10) - 10, 50); + this.drawBanner(contentWidth, contentLeft, 10, this.width - (this.modList.right + 12 + 10) - 10, 50); // Draw mod name GlStateManager.pushMatrix(); GlStateManager.translate(contentLeft, 70, 0); GlStateManager.scale(2.0F, 2.0F, 2.0F); - drawString(this.fontRenderer, this.selectedModInfo.getName(), 0, 0, 0xFFFFFF); + drawString(this.fontRenderer, this.selectedModData.getName(), 0, 0, 0xFFFFFF); GlStateManager.popMatrix(); // Draw version - String modId = TextFormatting.DARK_GRAY + I18n.format("fml.menu.mods.info.modid", this.selectedModInfo.getModId()); + String modId = TextFormatting.DARK_GRAY + I18n.format("fml.menu.mods.info.modid", this.selectedModData.getModId()); int modIdWidth = this.fontRenderer.getStringWidth(modId); drawString(this.fontRenderer, modId, contentLeft + contentWidth - modIdWidth, 92, 0xFFFFFF); -// // Set tooltip for secure mod features forge has -// if (ScreenUtil.isMouseWithin(contentLeft + contentWidth - modIdWidth, 92, modIdWidth, this.font.lineHeight, mouseX, mouseY)) { -// if (FMLEnvironment.secureJarsEnabled) { -// this.setActiveTooltip(ForgeI18n.parseMessage("fml.menu.mods.info.signature", ((ModInfo) this.selectedModInfo).getOwningFile().getCodeSigningFingerprint().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.signature.unsigned")))); -// this.setActiveTooltip(ForgeI18n.parseMessage("fml.menu.mods.info.trust", ((ModInfo) this.selectedModInfo).getOwningFile().getTrustData().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.trust.noauthority")))); -// } else { -// this.setActiveTooltip(ForgeI18n.parseMessage("fml.menu.mods.info.securejardisabled")); -// } -// } - // Draw version - String displayVersion = this.selectedModInfo.getDisplayVersion(); + String displayVersion = this.selectedModData.getDisplayVersion(); this.drawStringWithLabel("fml.menu.mods.info.version", displayVersion, contentLeft, 92, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); // Draw inner version if the display version is different from it int versionWidth = this.fontRenderer.getStringWidth(I18n.format("fml.menu.mods.info.version", displayVersion)); - String innerVersion = this.selectedModInfo.getVersion(); - if (!displayVersion.equals(innerVersion) && ScreenUtil.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { + String innerVersion = this.selectedModData.getVersion(); + if (!displayVersion.equals(innerVersion) && ClientHelper.isMouseWithin(contentLeft, 92, versionWidth, this.fontRenderer.FONT_HEIGHT, mouseX, mouseY)) { this.setActiveTooltip(innerVersion); } // Draws an icon if there is an update for the mod - ForgeVersion.CheckResult result = ForgeVersion.getCleanResult(this.selectedModInfo); - if (shouldDraw(result) && result.url != null) { + ForgeVersion.CheckResult update = ForgeVersion.getCleanResult(this.selectedModData); + if (shouldDraw(update) && update.url != null) { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); mc.getTextureManager().bindTexture(VERSION_CHECK_ICONS); - int vOffset = result.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; - ScreenUtil.blit(contentLeft + versionWidth + 5, 92, result.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); - if (ScreenUtil.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { - switch (result.status) { + int vOffset = update.status.isAnimated() && (System.currentTimeMillis() / 800 & 1) == 1 ? 8 : 0; + ClientHelper.blit(contentLeft + versionWidth + 5, 92, update.status.getSheetOffset() * 8, vOffset, 8, 8, 64, 16); + if (ClientHelper.isMouseWithin(contentLeft + versionWidth + 5, 92, 8, 8, mouseX, mouseY)) { + switch (update.status) { case BETA: this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.beta")); break; case AHEAD: - this.setActiveTooltip(TextFormatting.LIGHT_PURPLE + I18n.format("fml.menu.mods.info.ahead", result.latestFound)); + this.setActiveTooltip(TextFormatting.LIGHT_PURPLE + I18n.format("fml.menu.mods.info.ahead", update.latestFound)); break; case BETA_OUTDATED: - if (result.homepage != null) { - this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.betaupdateavailable", result.latestFound, result.homepage)); + if (update.homepage != null) { + this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.betaupdateavailable", update.latestFound, update.homepage)); } else { - this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.betaupdateavailablenopage", result.latestFound)); + this.setActiveTooltip(TextFormatting.GOLD + I18n.format("fml.menu.mods.info.betaupdateavailablenopage", update.latestFound)); } break; case OUTDATED: - if (result.homepage != null) { - this.setActiveTooltip(TextFormatting.GREEN + I18n.format("fml.menu.mods.info.updateavailable", result.latestFound, result.homepage)); + if (update.homepage != null) { + this.setActiveTooltip(TextFormatting.GREEN + I18n.format("fml.menu.mods.info.updateavailable", update.latestFound, update.homepage)); } else { - this.setActiveTooltip(TextFormatting.GREEN + I18n.format("fml.menu.mods.info.updateavailablenopage", result.latestFound)); + this.setActiveTooltip(TextFormatting.GREEN + I18n.format("fml.menu.mods.info.updateavailablenopage", update.latestFound)); } break; } } } - ModMetadata metadata = selectedModInfo.getMetadata(); + ModMetadata metadata = selectedModData.getMetadata(); if (metadata != null && !metadata.autogenerated) { int labelOffset = this.height - 20; @@ -730,7 +723,7 @@ private void drawStringWithLabel(String format, String text, int x, int y, int m String credits = labelColor + label; credits += contentColor + content; drawString(this.fontRenderer, credits, x, y, 0xFFFFFF); - if (ScreenUtil.isMouseWithin(x, y, maxWidth, 9, mouseX, mouseY)) { // Sets the active tool tip if string is too long so users can still read it + if (ClientHelper.isMouseWithin(x, y, maxWidth, 9, mouseX, mouseY)) { // Sets the active tool tip if string is too long so users can still read it this.setActiveTooltip(text); } } else { @@ -738,58 +731,58 @@ private void drawStringWithLabel(String format, String text, int x, int y, int m } } - private void loadAndCacheLogo(ModContainer info) { - if (LOGO_CACHE.containsKey(info.getModId())) return; + private void loadAndCacheLogo(ModContainer data) { + if (BANNER_CACHE.containsKey(data.getModId())) return; // Fills an empty logo as logo may not be present - LOGO_CACHE.put(info.getModId(), Pair.of(null, new Size2i(0, 0))); + BANNER_CACHE.put(data.getModId(), Pair.of(null, new Dimension(0, 0))); // Attempts to load the real logo - ModMetadata metadata = info.getMetadata(); + ModMetadata metadata = data.getMetadata(); if (metadata == null) return; - String s = metadata.logoFile; - if (s.isEmpty()) return; + String logoFile = metadata.logoFile; + if (logoFile.isEmpty()) return; - IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); - BufferedImage logo = null; + IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(data.getModId()); + BufferedImage image = null; try { - if (resourcePack != null && !s.startsWith("/")) { - logo = resourcePack.getPackImage(); + if (resourcePack != null && !logoFile.startsWith("/")) { + image = resourcePack.getPackImage(); } else { - if (!s.startsWith("/")) { - s = "/" + s; + if (!logoFile.startsWith("/")) { + logoFile = "/" + logoFile; } - InputStream is = getClass().getResourceAsStream(s); - if (is != null) logo = TextureUtil.readBufferedImage(is); + InputStream is = getClass().getResourceAsStream(logoFile); + if (is != null) image = TextureUtil.readBufferedImage(is); } - if (logo == null) return; + if (image == null) return; TextureManager textureManager = this.mc.getTextureManager(); - LOGO_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("modlogo", this.createLogoTexture(logo, metadata.logoBlur)), new Size2i(logo.getWidth(), logo.getHeight()))); + BANNER_CACHE.put(data.getModId(), Pair.of(textureManager.getDynamicTextureLocation("modlogo", this.createLogoTexture(image, metadata.logoBlur)), new Dimension(image.getWidth(), image.getHeight()))); } catch (IOException ignored) { } } - private void loadAndCacheIcon(ModContainer info) { - if (ICON_CACHE.containsKey(info.getModId())) return; + private void loadAndCacheIcon(ModContainer data) { + if (IMAGE_ICON_CACHE.containsKey(data.getModId())) return; // Fills an empty icon as icon may not be present - ICON_CACHE.put(info.getModId(), Pair.of(null, new Size2i(0, 0))); + IMAGE_ICON_CACHE.put(data.getModId(), Pair.of(null, new Dimension(0, 0))); - ModMetadata metadata = info.getMetadata(); + ModMetadata metadata = data.getMetadata(); if (metadata == null) return; // Attempts to load the real icon - String iconString = metadata.iconFile; - if (!iconString.isEmpty()) { - if (!iconString.startsWith("/")) { - iconString = "/" + iconString; + String iconFile = metadata.iconFile; + if (!iconFile.isEmpty()) { + if (!iconFile.startsWith("/")) { + iconFile = "/" + iconFile; } - BufferedImage icon = null; - try (InputStream is = getClass().getResourceAsStream(iconString)) { - if (is != null) icon = TextureUtil.readBufferedImage(is); - if (icon != null) { + BufferedImage image = null; + try (InputStream is = getClass().getResourceAsStream(iconFile)) { + if (is != null) image = TextureUtil.readBufferedImage(is); + if (image != null) { TextureManager textureManager = this.mc.getTextureManager(); - ICON_CACHE.put(info.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(icon, metadata.iconBlur)), new Size2i(icon.getWidth(), icon.getHeight()))); + IMAGE_ICON_CACHE.put(data.getModId(), Pair.of(textureManager.getDynamicTextureLocation("catalogueicon", this.createLogoTexture(image, metadata.iconBlur)), new Dimension(image.getWidth(), image.getHeight()))); return; } } catch (IOException ignored) { @@ -797,46 +790,46 @@ private void loadAndCacheIcon(ModContainer info) { } // Attempts to use the logo file if it's a square - String logoString = metadata.logoFile; - if (!logoString.isEmpty()) { - IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(info.getModId()); - BufferedImage logo = null; + String logoFile = metadata.logoFile; + if (!logoFile.isEmpty()) { + IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(data.getModId()); + BufferedImage image = null; try { - if (resourcePack != null && !logoString.startsWith("/")) { - logo = resourcePack.getPackImage(); + if (resourcePack != null && !logoFile.startsWith("/")) { + image = resourcePack.getPackImage(); } else { - if (!logoString.startsWith("/")) { - logoString = "/" + logoString; + if (!logoFile.startsWith("/")) { + logoFile = "/" + logoFile; } - InputStream is = getClass().getResourceAsStream(logoString); - if (is != null) logo = TextureUtil.readBufferedImage(is); + InputStream is = getClass().getResourceAsStream(logoFile); + if (is != null) image = TextureUtil.readBufferedImage(is); } - if (logo != null && logo.getWidth() == logo.getHeight()) { + if (image != null && image.getWidth() == image.getHeight()) { TextureManager textureManager = this.mc.getTextureManager(); - String modId = info.getModId(); + String modId = data.getModId(); /* The first selected mod will have its logo cached before the icon, so we * can just use the logo instead of loading the image again. */ - if (LOGO_CACHE.containsKey(modId)) { - if (LOGO_CACHE.get(modId).getLeft() != null) { - ICON_CACHE.put(modId, LOGO_CACHE.get(modId)); + if (BANNER_CACHE.containsKey(modId)) { + if (BANNER_CACHE.get(modId).getLeft() != null) { + IMAGE_ICON_CACHE.put(modId, BANNER_CACHE.get(modId)); return; } } /* Since the icon will be same as the logo, we can cache into both icon and logo cache */ - DynamicTexture texture = this.createLogoTexture(logo, metadata.logoBlur); - Size2i size = new Size2i(logo.getWidth(), logo.getHeight()); + DynamicTexture texture = this.createLogoTexture(image, metadata.logoBlur); + Dimension size = new Dimension(image.getWidth(), image.getHeight()); ResourceLocation textureId = textureManager.getDynamicTextureLocation("catalogueicon", texture); - ICON_CACHE.put(modId, Pair.of(textureId, size)); - LOGO_CACHE.put(modId, Pair.of(textureId, size)); + IMAGE_ICON_CACHE.put(modId, Pair.of(textureId, size)); + BANNER_CACHE.put(modId, Pair.of(textureId, size)); } } catch (IOException ignored) { } } } - private void loadAndCacheBackground(ModContainer info) { + private void loadAndCacheBackground(ModContainer data) { // Deletes the last cached background since they are large images if (cachedBackground != null) { TextureManager textureManager = this.mc.getTextureManager(); @@ -844,17 +837,17 @@ private void loadAndCacheBackground(ModContainer info) { } cachedBackground = null; - ModMetadata metadata = info.getMetadata(); + ModMetadata metadata = data.getMetadata(); if (metadata == null) return; - String s = metadata.backgroundFile; - if(s.isEmpty()) return; + String background = metadata.backgroundFile; + if(background.isEmpty()) return; - if (!s.startsWith("/")) { - s = "/" + s; + if (!background.startsWith("/")) { + background = "/" + background; } BufferedImage image = null; - try(InputStream is = getClass().getResourceAsStream(s)) { + try (InputStream is = getClass().getResourceAsStream(background)) { if (is != null) image = TextureUtil.readBufferedImage(is); if (image == null || image.getWidth() != 512 || image.getHeight() != 256) return; TextureManager textureManager = this.mc.getTextureManager(); @@ -872,8 +865,9 @@ public void updateDynamicTexture() { }; } + @SuppressWarnings("SameParameterValue") private void drawBackground(int contentWidth, int x, int y) { - if(this.selectedModInfo == null) return; + if(this.selectedModData == null) return; if(cachedBackground == null) return; this.mc.renderEngine.bindTexture(cachedBackground); @@ -893,13 +887,13 @@ private void drawBackground(int contentWidth, int x, int y) { } @SuppressWarnings("SameParameterValue") - private void drawLogo(int contentWidth, int x, int y, int maxWidth, int maxHeight) { - if (this.selectedModInfo != null) { + private void drawBanner(int contentWidth, int x, int y, int maxWidth, int maxHeight) { + if (this.selectedModData != null) { ResourceLocation logoResource = MISSING_BANNER; - Size2i size = new Size2i(600, 120); + Dimension size = new Dimension(600, 120); - if (LOGO_CACHE.containsKey(this.selectedModInfo.getModId())) { - Pair logoInfo = LOGO_CACHE.get(this.selectedModInfo.getModId()); + if (BANNER_CACHE.containsKey(this.selectedModData.getModId())) { + Pair logoInfo = BANNER_CACHE.get(this.selectedModData.getModId()); if (logoInfo.getLeft() != null) { logoResource = logoInfo.getLeft(); size = logoInfo.getRight(); @@ -924,7 +918,7 @@ private void drawLogo(int contentWidth, int x, int y, int maxWidth, int maxHeigh x += (contentWidth - width) / 2; y += (maxHeight - height) / 2; - ScreenUtil.blit(x, y, width, height, 0.0F, 0.0F, size.width, size.height, size.width, size.height); + ClientHelper.blit(x, y, width, height, 0.0F, 0.0F, size.width, size.height, size.width, size.height); GlStateManager.disableBlend(); } @@ -935,21 +929,21 @@ private void setActiveTooltip(String content) { this.tooltipYOffset = 0; } - private void setSelectedModInfo(ModContainer selectedModInfo) { - this.selectedModInfo = selectedModInfo; - this.loadAndCacheLogo(selectedModInfo); - this.loadAndCacheBackground(selectedModInfo); + private void setSelectedModData(ModContainer data) { + this.selectedModData = data; + this.loadAndCacheLogo(data); + this.loadAndCacheBackground(data); this.configButton.visible = true; this.websiteButton.visible = true; this.issueButton.visible = true; this.configButton.enabled = false; - IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedModInfo); + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(data); if (guiFactory != null) { this.configButton.enabled = guiFactory.hasConfigGui(); } - ModMetadata metadata = selectedModInfo.getMetadata(); + ModMetadata metadata = data.getMetadata(); if (metadata != null && !metadata.autogenerated) { this.websiteButton.enabled = !metadata.url.isEmpty(); this.issueButton.enabled = !metadata.issueTrackerUrl.isEmpty(); @@ -957,10 +951,10 @@ private void setSelectedModInfo(ModContainer selectedModInfo) { int contentLeft = this.modList.right + 12 + 10; int contentWidth = this.width - contentLeft - 10; - int labelCount = this.getLabelCount(selectedModInfo); + int labelCount = this.getLabelCount(data); this.descriptionList.updateSize(contentWidth, this.height - 135 - 10 - labelCount * 15, 130, this.height - 10 - labelCount * 15); this.descriptionList.setSlotXBoundsFromLeft(contentLeft); - this.descriptionList.setTextFromInfo(selectedModInfo); + this.descriptionList.setTextFromInfo(data); this.descriptionList.setAmountScrolled(0); } @@ -976,7 +970,7 @@ private int getLabelCount(ModContainer selectedModInfo) { } private void updateSelectedModList() { - ModEntry selectedEntry = this.modList.getEntryFromInfo(this.selectedModInfo); + ModListEntry selectedEntry = this.modList.getEntryFromInfo(this.selectedModData); if (selectedEntry != null) { this.modList.selectMod(selectedEntry); } @@ -986,8 +980,8 @@ private void updateSearchField(String value) { if (value.isEmpty()) { this.searchTextField.setSuggestion(I18n.format("fml.menu.mods.searchwithdots")); } else { - Optional optional = Loader.instance().getActiveModList().stream().filter(info -> { - return info.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); + Optional optional = CACHED_MODS.values().stream().filter(data -> { + return data.getName().toLowerCase(Locale.ENGLISH).startsWith(value.toLowerCase(Locale.ENGLISH)); }).min(Comparator.comparing(ModContainer::getName)); if (optional.isPresent()) { int length = value.length(); @@ -1022,13 +1016,13 @@ private void openLink(String url) { this.handleComponentClick(new TextComponentString("").setStyle(style)); } - private boolean shouldDraw(ForgeVersion.CheckResult result) { - return result != null && result.status.shouldDraw(); + private boolean shouldDraw(ForgeVersion.CheckResult update) { + return update != null && update.status.shouldDraw(); } - private boolean shouldUpdate(ForgeVersion.CheckResult result) { - if (result == null) return false; - ForgeVersion.Status status = result.status; + private boolean shouldUpdate(ForgeVersion.CheckResult update) { + if (update == null) return false; + ForgeVersion.Status status = update.status; return status == ForgeVersion.Status.OUTDATED || status == ForgeVersion.Status.BETA_OUTDATED; } } From 4c5057d61b4d4a6834542966a3b6dbf0d3d79c81 Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:50:08 +0800 Subject: [PATCH 15/91] Update CatalogueModListScreen.java --- .../TEMPmodlist/screen/CatalogueModListScreen.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 3e0832cf9..22701c670 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -740,19 +740,19 @@ private void loadAndCacheLogo(ModContainer data) { // Attempts to load the real logo ModMetadata metadata = data.getMetadata(); if (metadata == null) return; - String logoFile = metadata.logoFile; - if (logoFile.isEmpty()) return; + String banner = metadata.logoFile; + if (banner.isEmpty()) return; IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(data.getModId()); BufferedImage image = null; try { - if (resourcePack != null && !logoFile.startsWith("/")) { + if (resourcePack != null && !banner.startsWith("/")) { image = resourcePack.getPackImage(); } else { - if (!logoFile.startsWith("/")) { - logoFile = "/" + logoFile; + if (!banner.startsWith("/")) { + banner = "/" + banner; } - InputStream is = getClass().getResourceAsStream(logoFile); + InputStream is = getClass().getResourceAsStream(banner); if (is != null) image = TextureUtil.readBufferedImage(is); } if (image == null) return; From fbc15c1adab4348a0885ac5d16bf564b09ef296f Mon Sep 17 00:00:00 2001 From: RuiXuqi <90179819+RuiXuqi@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:14:34 +0800 Subject: [PATCH 16/91] Cherry-pick new features back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📦 Added missing background when no texture provided 📦 Icon will now be used if no banner texture is available 🎨 Changed missing banner 📦 Improved design ✨ Last search is now remembered when returning to menu https://github.com/MrCrayfish/Catalogue/commit/6260949324967c10fdbd5126c27509eabfa50d96 https://github.com/MrCrayfish/Catalogue/commit/37db4320cba0b0847eb31d194d2d670e7916bafd https://github.com/MrCrayfish/Catalogue/commit/c22fd3d61bfd45aed871762784a4ce9525e53f1a https://github.com/MrCrayfish/Catalogue/commit/6b11e0f0129fe113c6d00b46bb923d71a6594cff https://github.com/MrCrayfish/Catalogue/commit/3fcc8edd021033cb03c15584d7fbb9836070cdc6 Also make the test mod a dummy. It is not loaded at all. --- .../screen}/CatalogueContainer.java | 10 +- .../screen/CatalogueModListScreen.java | 191 ++++++++++++++---- .../screen/MinecraftContainer.java | 23 +++ .../screen/widget/CatalogueListExtended.java | 190 ++++++++++------- .../net/minecraftforge/fml/common/Loader.java | 3 - .../assets/forge/textures/gui/minecraft.png | Bin 0 -> 34429 bytes .../forge/textures/gui/missing_background.png | Bin 0 -> 67119 bytes .../forge/textures/gui/missing_banner.png | Bin 57786 -> 1217 bytes 8 files changed, 294 insertions(+), 123 deletions(-) rename src/main/java/{com/cleanroommc/common => net/minecraftforge/fml/client/TEMPmodlist/screen}/CatalogueContainer.java (81%) create mode 100644 src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/MinecraftContainer.java create mode 100644 src/main/resources/assets/forge/textures/gui/minecraft.png create mode 100644 src/main/resources/assets/forge/textures/gui/missing_background.png diff --git a/src/main/java/com/cleanroommc/common/CatalogueContainer.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueContainer.java similarity index 81% rename from src/main/java/com/cleanroommc/common/CatalogueContainer.java rename to src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueContainer.java index 84f41182c..8544f6298 100644 --- a/src/main/java/com/cleanroommc/common/CatalogueContainer.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueContainer.java @@ -1,12 +1,11 @@ -package com.cleanroommc.common; +package net.minecraftforge.fml.client.TEMPmodlist.screen; -import com.google.common.eventbus.EventBus; import net.minecraftforge.fml.common.DummyModContainer; -import net.minecraftforge.fml.common.LoadController; import net.minecraftforge.fml.common.ModMetadata; import java.util.Arrays; +// A dummy mod only displayed in list public class CatalogueContainer extends DummyModContainer { public CatalogueContainer() { super(new ModMetadata()); @@ -27,9 +26,4 @@ public CatalogueContainer() { meta.iconItem = "minecraft:diamond_sword"; meta.backgroundFile = "/catalogue_background.png"; } - - @Override - public boolean registerBus(EventBus bus, LoadController controller) { - return true; - } } diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java index 22701c670..f387b3f0f 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/CatalogueModListScreen.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiOptions; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -50,13 +51,16 @@ public class CatalogueModListScreen extends GuiScreen { private static final ResourceLocation MISSING_BANNER = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/missing_banner.png"); + private static final ResourceLocation MISSING_BACKGROUND = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/missing_background.png"); private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png"); + private static final ResourceLocation MINECRAFT_LOGO = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/minecraft.png"); private static final Map> BANNER_CACHE = new HashMap<>(); private static final Map> IMAGE_ICON_CACHE = new HashMap<>(); private static final Map ITEM_ICON_CACHE = new HashMap<>(); private static final Map CACHED_MODS = new HashMap<>(); private static ResourceLocation cachedBackground; private static boolean loaded = false; + private static String lastSearch = ""; private final GuiScreen parentScreen; private CatalogueTextField searchTextField; @@ -79,6 +83,8 @@ public CatalogueModListScreen(GuiScreen parent) { this.parentScreen = parent; if(!loaded) { Loader.instance().getActiveModList().forEach(data -> CACHED_MODS.put(data.getModId(), data)); + CACHED_MODS.put("minecraft", new MinecraftContainer()); // Override minecraft + CACHED_MODS.put("catalogue", new CatalogueContainer()); loaded = true; } } @@ -89,10 +95,11 @@ public void initGui() { this.searchTextField = new CatalogueTextField(0, this.fontRenderer, 11, 25, 148, 20); this.searchTextField.setCanLoseFocus(true); this.searchTextField.setEnableBackgroundDrawing(true); + this.searchTextField.setText(lastSearch); this.modList = new ModList(); this.modList.setSlotXBoundsFromLeft(10); - this.modList.setDrawTopAndBottom(false); + this.modList.registerScrollButtons(7, 8); this.buttonList.add(new GuiButton(1, 10, modList.bottom + 8, 127, 20, I18n.format("gui.back"))); this.modFolderButton = this.addButton(new CatalogueIconButton(2, 140, modList.bottom + 8, 0, 0)); @@ -111,9 +118,8 @@ public void initGui() { this.issueButton = this.addButton(new CatalogueIconButton(5, contentLeft + buttonWidth + buttonWidth + 10, 105, 30, 0, buttonWidth, I18n.format("fml.menu.mods.issue"))); this.issueButton.visible = false; - this.descriptionList = new StringList(contentWidth, this.height - 135 - 55, contentLeft, 130); - this.descriptionList.setDrawTopAndBottom(false); - this.descriptionList.setDrawBackground(false); + this.descriptionList = new StringList(contentWidth + padding * 2, 50, contentLeft - padding, 130); + this.descriptionList.registerScrollButtons(9, 10); this.updatesButton = this.addButton(new CatalogueCheckBoxButton(6, this.modList.right - 14, 7, false)); @@ -145,12 +151,16 @@ public void actionPerformed(GuiButton button) { } break; case 3: - try { - IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedModData); - GuiScreen newScreen = guiFactory.createConfigGui(this); - this.mc.displayGuiScreen(newScreen); - } catch (Exception e) { - FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", selectedModData.getModId(), e); + if (!this.selectedModData.getModId().equals("minecraft")) { + try { + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(this.selectedModData); + GuiScreen newScreen = guiFactory.createConfigGui(this); + this.mc.displayGuiScreen(newScreen); + } catch (Exception e) { + FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", this.selectedModData.getModId(), e); + } + } else { + this.mc.displayGuiScreen(new GuiOptions(this, this.mc.gameSettings)); } break; case 4: @@ -205,6 +215,8 @@ protected void keyTyped(char typedChar, int key) throws IOException { String s = this.searchTextField.getText(); this.updateSearchField(s); this.modList.filterAndUpdateList(s); + this.updateSelectedModList(); + lastSearch = s; return; } @@ -221,7 +233,7 @@ public ModList() { @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { - ClientHelper.scissor(this.left, this.top, this.width, this.height); + ClientHelper.scissor(this.getListLeft(), this.top, this.width, this.bottom - this.top); super.drawScreen(mouseX, mouseY, partialTicks); GL11.glDisable(GL11.GL_SCISSOR_TEST); } @@ -270,9 +282,24 @@ public int getEntryIndex(IGuiListEntry entry) { return this.entries.indexOf(entry); } + @Override + protected int getScrollBarX() { + return this.left + this.width - 6; + } + + @Override + public int getListLeft() { + return this.left; + } + + @Override + public int getListRight() { + return this.getListLeft() + this.getListWidth(); + } + @Override public int getListWidth() { - return this.width; // Why it is 220 by default??? + return this.width - (this.isScrollBarVisible() ? 6 : 0); } @Override @@ -286,8 +313,11 @@ protected boolean isSelected(int slotIndex) { } @Override - protected int getScrollBarX() { - return this.left + this.width - 6; + protected void drawTopAndBottom(Tessellator tessellator) { + } + + @Override + protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { } } @@ -302,7 +332,6 @@ public ModListEntry(ModContainer data, ModList list) { @Override public void drawEntry(int index, int left, int top, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean hovered, float partialTicks) { - left -= 2; // Move 2px, the borders. // Draws mod name and version drawString(fontRenderer, this.getFormattedModName(), left + 24, top + 2, 0xFFFFFF); drawString(fontRenderer, TextFormatting.GRAY + this.data.getDisplayVersion(), left + 24, top + 12, 0xFFFFFF); @@ -375,7 +404,7 @@ private ItemStack getItemIcon() { ModMetadata metadata = this.data.getMetadata(); if (metadata != null && !metadata.autogenerated) { String itemIcon = metadata.iconItem; - if (!itemIcon.isEmpty()) { + if (!itemIcon.isBlank()) { try { // 0:mod id 1:item name (2:metadata) String[] parts = itemIcon.split(":"); @@ -435,12 +464,18 @@ private class StringList extends CatalogueListExtended { private List entries = Lists.newArrayList(); public StringList(int width, int height, int left, int top) { - super(CatalogueModListScreen.this.mc, width, CatalogueModListScreen.this.height, top, top + height, 10); - this.setSlotXBoundsFromLeft(left); + super(CatalogueModListScreen.this.mc, width, height, top, top + height, 10); + this.setSlotXBoundsFromLeft(left + 8); + this.visible = false; } public void setTextFromInfo(ModContainer data) { this.entries.clear(); + this.visible = true; + if (data.getMetadata().description.trim().isBlank()) { + this.visible = false; + return; + } String description = data.getMetadata().description.trim(); List lines = CatalogueModListScreen.this.fontRenderer.listFormattedStringToWidth(description, this.getListWidth()); @@ -457,25 +492,59 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { GL11.glDisable(GL11.GL_SCISSOR_TEST); } + @Override + protected void drawContainerBackground(Tessellator tessellator) { + int x = this.left; + int y = this.top; + int width = this.width; + int height = this.height; + drawRect(x, y + 1, x + 1, y + height - 1, 0x77000000); + drawRect(x + 1, y, x + width - 1, y + height, 0x77000000); + drawRect(x + width - 1, y + 1, x + width, y + height - 1, 0x77000000); + } + @Override protected int getScrollBarX() { return this.left + this.width - 7; } @Override - protected int getSize() { - return this.entries.size(); + public int getListLeft() { + return this.left + 8; } @Override public int getListWidth() { - return this.width - 10; + return this.width - 16; + } + + @Override + protected int getRowTop(int $$0) { + return super.getRowTop($$0) + 4; + } + + @Override + public int getMaxScroll() { + return Math.max(0, this.getContentHeight() - (this.height - 12)); + } + + @Override + protected int getSize() { + return this.entries.size(); } @Override public IGuiListEntry getListEntry(int index) { return this.entries.get(index); } + + @Override + protected void drawTopAndBottom(Tessellator tessellator) { + } + + @Override + protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { + } } private class StringEntry implements CatalogueListExtended.IGuiListEntry { @@ -540,6 +609,7 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti this.searchTextField.setText(""); this.updateSearchField(""); this.modList.filterAndUpdateList(""); + lastSearch = ""; return; } // Left click to apply suggestions @@ -551,6 +621,7 @@ protected void mouseClicked(int mouseX, int mouseY, int button) throws IOExcepti this.searchTextField.setText(text); this.updateSearchField(text); this.modList.filterAndUpdateList(text); + lastSearch = text; this.lastClickTime = currentTine; return; } @@ -672,25 +743,28 @@ private void drawModInfo(int mouseX, int mouseY, float partialTicks) { ModMetadata metadata = selectedModData.getMetadata(); if (metadata != null && !metadata.autogenerated) { - int labelOffset = this.height - 20; + // Draw fade from the bottom + drawGradientRect(this.modList.right + 12, this.height - 50, this.width, this.height, 0x00000000, 0x66000000); + + int labelOffset = this.height - 18; // Draw license String license = metadata.license; - if (!license.isEmpty()) { + if (!license.isBlank()) { this.drawStringWithLabel("fml.menu.mods.info.license", license, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); labelOffset -= 15; } // Draw credits String credits = metadata.credits; - if (!credits.isEmpty()) { + if (!credits.isBlank()) { this.drawStringWithLabel("fml.menu.mods.info.credits", credits, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); labelOffset -= 15; } // Draw authors String authors = metadata.getAuthorList(); - if (!authors.isEmpty()) { + if (!authors.isBlank()) { this.drawStringWithLabel("fml.menu.mods.info.authors", authors, contentLeft, labelOffset, contentWidth, mouseX, mouseY, TextFormatting.GRAY, TextFormatting.WHITE); } } @@ -741,7 +815,7 @@ private void loadAndCacheLogo(ModContainer data) { ModMetadata metadata = data.getMetadata(); if (metadata == null) return; String banner = metadata.logoFile; - if (banner.isEmpty()) return; + if (banner.isBlank()) return; IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(data.getModId()); BufferedImage image = null; @@ -773,7 +847,7 @@ private void loadAndCacheIcon(ModContainer data) { // Attempts to load the real icon String iconFile = metadata.iconFile; - if (!iconFile.isEmpty()) { + if (!iconFile.isBlank()) { if (!iconFile.startsWith("/")) { iconFile = "/" + iconFile; } @@ -791,7 +865,7 @@ private void loadAndCacheIcon(ModContainer data) { // Attempts to use the logo file if it's a square String logoFile = metadata.logoFile; - if (!logoFile.isEmpty()) { + if (!logoFile.isBlank()) { IResourcePack resourcePack = FMLClientHandler.instance().getResourcePackFor(data.getModId()); BufferedImage image = null; try { @@ -840,7 +914,7 @@ private void loadAndCacheBackground(ModContainer data) { ModMetadata metadata = data.getMetadata(); if (metadata == null) return; String background = metadata.backgroundFile; - if(background.isEmpty()) return; + if(background.isBlank()) return; if (!background.startsWith("/")) { background = "/" + background; @@ -868,11 +942,14 @@ public void updateDynamicTexture() { @SuppressWarnings("SameParameterValue") private void drawBackground(int contentWidth, int x, int y) { if(this.selectedModData == null) return; - if(cachedBackground == null) return; - this.mc.renderEngine.bindTexture(cachedBackground); + ResourceLocation texture = cachedBackground != null ? cachedBackground : MISSING_BACKGROUND; + this.mc.renderEngine.bindTexture(texture); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.shadeModel(GL11.GL_SMOOTH); + GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); Tessellator tessellator = Tessellator.getInstance(); BufferBuilder builder = tessellator.getBuffer(); @@ -884,13 +961,15 @@ private void drawBackground(int contentWidth, int x, int y) { tessellator.draw(); GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.shadeModel(GL11.GL_FLAT); } @SuppressWarnings("SameParameterValue") private void drawBanner(int contentWidth, int x, int y, int maxWidth, int maxHeight) { if (this.selectedModData != null) { ResourceLocation logoResource = MISSING_BANNER; - Dimension size = new Dimension(600, 120); + Dimension size = new Dimension(120, 120); if (BANNER_CACHE.containsKey(this.selectedModData.getModId())) { Pair logoInfo = BANNER_CACHE.get(this.selectedModData.getModId()); @@ -900,12 +979,29 @@ private void drawBanner(int contentWidth, int x, int y, int maxWidth, int maxHei } } + int scale = 1; + if (logoResource == MISSING_BANNER) { + Pair logoInfo = IMAGE_ICON_CACHE.get(this.selectedModData.getModId()); + if (logoInfo.getLeft() != null) { + logoResource = logoInfo.getLeft(); + size = logoInfo.getRight(); + scale = 10; // Hack to make icon fill max banner height + } + } + + boolean offset = false; + if (this.selectedModData.getModId().equals("minecraft")) { + logoResource = MINECRAFT_LOGO; + size = new Dimension(1024, 256); + offset = true; + } + mc.getTextureManager().bindTexture(logoResource); GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.enableBlend(); - int width = size.width; - int height = size.height; + int width = size.width * scale; + int height = size.height * scale; if (size.width > maxWidth) { width = maxWidth; height = (width * size.height) / size.width; @@ -918,6 +1014,10 @@ private void drawBanner(int contentWidth, int x, int y, int maxWidth, int maxHei x += (contentWidth - width) / 2; y += (maxHeight - height) / 2; + if (offset) { // Fix for minecraft logo + y += 8; + } + ClientHelper.blit(x, y, width, height, 0.0F, 0.0F, size.width, size.height, size.width, size.height); GlStateManager.disableBlend(); @@ -937,22 +1037,27 @@ private void setSelectedModData(ModContainer data) { this.websiteButton.visible = true; this.issueButton.visible = true; - this.configButton.enabled = false; - IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(data); - if (guiFactory != null) { - this.configButton.enabled = guiFactory.hasConfigGui(); + if (!this.selectedModData.getModId().equals("minecraft")) { + this.configButton.enabled = false; + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(data); + if (guiFactory != null) { + this.configButton.enabled = guiFactory.hasConfigGui(); + } + } else { + this.configButton.enabled = true; } ModMetadata metadata = data.getMetadata(); if (metadata != null && !metadata.autogenerated) { - this.websiteButton.enabled = !metadata.url.isEmpty(); - this.issueButton.enabled = !metadata.issueTrackerUrl.isEmpty(); + this.websiteButton.enabled = !metadata.url.isBlank(); + this.issueButton.enabled = !metadata.issueTrackerUrl.isBlank(); } int contentLeft = this.modList.right + 12 + 10; int contentWidth = this.width - contentLeft - 10; int labelCount = this.getLabelCount(data); - this.descriptionList.updateSize(contentWidth, this.height - 135 - 10 - labelCount * 15, 130, this.height - 10 - labelCount * 15); + this.descriptionList.setWidth(contentWidth); + this.descriptionList.setHeight(this.height - 135 - labelCount * 15 - 9); this.descriptionList.setSlotXBoundsFromLeft(contentLeft); this.descriptionList.setTextFromInfo(data); this.descriptionList.setAmountScrolled(0); @@ -962,8 +1067,8 @@ private int getLabelCount(ModContainer selectedModInfo) { int count = 0; ModMetadata metadata = selectedModInfo.getMetadata(); if (metadata != null && !metadata.autogenerated) { - if (!metadata.license.isEmpty()) count++; - if (!metadata.credits.isEmpty()) count++; + if (!metadata.license.isBlank()) count++; + if (!metadata.credits.isBlank()) count++; if (!metadata.authorList.isEmpty()) count++; } return count; diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/MinecraftContainer.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/MinecraftContainer.java new file mode 100644 index 000000000..88d5f5342 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/MinecraftContainer.java @@ -0,0 +1,23 @@ +package net.minecraftforge.fml.client.TEMPmodlist.screen; + +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.common.DummyModContainer; +import net.minecraftforge.fml.common.ModMetadata; + +import java.util.List; + +public class MinecraftContainer extends DummyModContainer { + public MinecraftContainer() { + super(new ModMetadata()); + ModMetadata meta = this.getMetadata(); + meta.modId = "minecraft"; + meta.name = "Minecraft"; + // Description provided by minecraft.wiki (CC BY-NC-SA 3.0) + meta.description = "Minecraft is a 3D sandbox adventure game developed by Mojang Studios where players can interact with a fully customizable three-dimensional world made of blocks and entities. Its diverse gameplay options allow players to choose the way they play, creating countless possibilities."; + meta.version = ForgeVersion.mcVersion; + meta.authorList = List.of("Mojang AB"); + meta.url = "https://www.minecraft.net"; + meta.issueTrackerUrl = "https://bugs.mojang.com/projects/MC/issues"; + meta.license = "All Rights Reserved"; + } +} diff --git a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java index 5c8c0e3b7..d203ec769 100644 --- a/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java +++ b/src/main/java/net/minecraftforge/fml/client/TEMPmodlist/screen/widget/CatalogueListExtended.java @@ -12,8 +12,7 @@ public abstract class CatalogueListExtended extends GuiListExtended { // Noting different from the original one, but allows you to remove the shadow on the bottom and top. // Created to avoid mixins. - private boolean shouldDrawTopAndBottom = true; - private boolean shouldDrawBackground = true; + private boolean scrollBarVisible; public CatalogueListExtended(Minecraft mcIn, int widthIn, int heightIn, int topIn, int bottomIn, int slotHeightIn) { super(mcIn, widthIn, heightIn, topIn, bottomIn, slotHeightIn); @@ -30,32 +29,27 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { // Customized background. Empty by default. this.drawBackground(); - int scrollBarLeft = this.getScrollBarX(); - int scrollBarRight = scrollBarLeft + 6; - this.bindAmountScrolled(); + int maxScroll = this.getMaxScroll(); + this.scrollBarVisible = maxScroll > 0 && this.getContentHeight() != 0; + GlStateManager.disableLighting(); GlStateManager.disableFog(); Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder vertexBuffer = tessellator.getBuffer(); // Shadowed dirt background. Scroll with the entries. - if (this.shouldDrawBackground) { - this.drawContainerBackground(tessellator); - } - - int contentLeft = this.left + (this.shouldDrawBackground ? this.width / 2 - this.getListWidth() / 2 + 2 : 0); - int contentTop = this.top + 4 - (int)this.amountScrolled; + this.drawContainerBackground(tessellator); + // Customized header. Empty by default if (this.hasListHeader) { - this.drawListHeader(contentLeft, contentTop, tessellator); + this.drawListHeader(this.getListLeft(), this.getListTop(), tessellator); } - this.drawSelectionBox(contentLeft, contentTop, mouseX, mouseY, partialTicks); + this.drawSelectionBox(mouseX, mouseY, partialTicks); GlStateManager.disableDepth(); - // Draw overlay to hide scrolled entries + // Draw overlay dirt to hide scrolled entries this.overlayBackground(0, this.top, 255, 255); this.overlayBackground(this.bottom, this.height, 255, 255); @@ -66,41 +60,11 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { GlStateManager.disableTexture2D(); // Shadow the top and bottom - if (this.shouldDrawTopAndBottom) { - this.drawTopAndBottom(tessellator); - } + this.drawTopAndBottom(tessellator); // Scroll Bar - int maxScroll = this.getMaxScroll(); - if (maxScroll > 0 && this.getContentHeight() != 0) { - int scrollThumbHeight = (this.bottom - this.top) * (this.bottom - this.top) / this.getContentHeight(); - scrollThumbHeight = MathHelper.clamp(scrollThumbHeight, 32, this.bottom - this.top - 8); - int scrollThumbTop = (int)this.amountScrolled * (this.bottom - this.top - scrollThumbHeight) / maxScroll + this.top; - scrollThumbTop = Math.max(scrollThumbTop, this.top); - - // Background - vertexBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); - vertexBuffer.pos(scrollBarLeft, this.bottom, 0).tex(0, 1).color(0, 0, 0, 255).endVertex(); - vertexBuffer.pos(scrollBarRight, this.bottom, 0).tex(1, 1).color(0, 0, 0, 255).endVertex(); - vertexBuffer.pos(scrollBarRight, this.top, 0).tex(1, 0).color(0, 0, 0, 255).endVertex(); - vertexBuffer.pos(scrollBarLeft, this.top, 0).tex(0, 0).color(0, 0, 0, 255).endVertex(); - tessellator.draw(); - - // Main - vertexBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); - vertexBuffer.pos(scrollBarLeft, scrollThumbTop + scrollThumbHeight, 0).tex(0, 1).color(128, 128, 128, 255).endVertex(); - vertexBuffer.pos(scrollBarRight, scrollThumbTop + scrollThumbHeight, 0).tex(1, 1).color(128, 128, 128, 255).endVertex(); - vertexBuffer.pos(scrollBarRight, scrollThumbTop, 0).tex(1, 0).color(128, 128, 128, 255).endVertex(); - vertexBuffer.pos(scrollBarLeft, scrollThumbTop, 0).tex(0, 0).color(128, 128, 128, 255).endVertex(); - tessellator.draw(); - - // Border - vertexBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); - vertexBuffer.pos(scrollBarLeft, scrollThumbTop + scrollThumbHeight - 1, 0).tex(0, 1).color(192, 192, 192, 255).endVertex(); - vertexBuffer.pos(scrollBarRight - 1, scrollThumbTop + scrollThumbHeight - 1, 0).tex(1, 1).color(192, 192, 192, 255).endVertex(); - vertexBuffer.pos(scrollBarRight - 1, scrollThumbTop, 0).tex(1, 0).color(192, 192, 192, 255).endVertex(); - vertexBuffer.pos(scrollBarLeft, scrollThumbTop, 0).tex(0, 0).color(192, 192, 192, 255).endVertex(); - tessellator.draw(); + if (this.scrollBarVisible) { + this.drawScrollBar(tessellator, maxScroll); } // Customized decorations. Empty by default. @@ -115,43 +79,131 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { protected void drawTopAndBottom(Tessellator tessellator) { BufferBuilder buffer = tessellator.getBuffer(); buffer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); - buffer.pos((double)this.left, (double)(this.top + 4), 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 0).endVertex(); - buffer.pos((double)this.right, (double)(this.top + 4), 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 0).endVertex(); - buffer.pos((double)this.right, (double)this.top, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos((double)this.left, (double)this.top, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos(this.left, this.top + 4, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 0).endVertex(); + buffer.pos(this.right, this.top + 4, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 0).endVertex(); + buffer.pos(this.right, this.top, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos(this.left, this.top, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 255).endVertex(); tessellator.draw(); buffer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); - buffer.pos((double)this.left, (double)this.bottom, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos((double)this.right, (double)this.bottom, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 255).endVertex(); - buffer.pos((double)this.right, (double)(this.bottom - 4), 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 0).endVertex(); - buffer.pos((double)this.left, (double)(this.bottom - 4), 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 0).endVertex(); + buffer.pos(this.left, this.bottom, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos(this.right, this.bottom, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos(this.right, this.bottom - 4, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 0).endVertex(); + buffer.pos(this.left, this.bottom - 4, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 0).endVertex(); tessellator.draw(); } + protected void drawScrollBar(Tessellator tessellator, int maxScroll) { + BufferBuilder buffer = tessellator.getBuffer(); + + int scrollBarLeft = this.getScrollBarX(); + int scrollBarRight = scrollBarLeft + 6; + + int scrollThumbHeight = (this.bottom - this.top) * (this.bottom - this.top) / this.getContentHeight(); + scrollThumbHeight = MathHelper.clamp(scrollThumbHeight, 32, this.bottom - this.top - 8); + int scrollThumbTop = (int)this.amountScrolled * (this.bottom - this.top - scrollThumbHeight) / maxScroll + this.top; + scrollThumbTop = Math.max(scrollThumbTop, this.top); + + // Background + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + buffer.pos(scrollBarLeft, this.bottom, 0).tex(0, 1).color(0, 0, 0, 255).endVertex(); + buffer.pos(scrollBarRight, this.bottom, 0).tex(1, 1).color(0, 0, 0, 255).endVertex(); + buffer.pos(scrollBarRight, this.top, 0).tex(1, 0).color(0, 0, 0, 255).endVertex(); + buffer.pos(scrollBarLeft, this.top, 0).tex(0, 0).color(0, 0, 0, 255).endVertex(); + tessellator.draw(); + + // Main + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + buffer.pos(scrollBarLeft, scrollThumbTop + scrollThumbHeight, 0).tex(0, 1).color(128, 128, 128, 255).endVertex(); + buffer.pos(scrollBarRight, scrollThumbTop + scrollThumbHeight, 0).tex(1, 1).color(128, 128, 128, 255).endVertex(); + buffer.pos(scrollBarRight, scrollThumbTop, 0).tex(1, 0).color(128, 128, 128, 255).endVertex(); + buffer.pos(scrollBarLeft, scrollThumbTop, 0).tex(0, 0).color(128, 128, 128, 255).endVertex(); + tessellator.draw(); + + // Border + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + buffer.pos(scrollBarLeft, scrollThumbTop + scrollThumbHeight - 1, 0).tex(0, 1).color(192, 192, 192, 255).endVertex(); + buffer.pos(scrollBarRight - 1, scrollThumbTop + scrollThumbHeight - 1, 0).tex(1, 1).color(192, 192, 192, 255).endVertex(); + buffer.pos(scrollBarRight - 1, scrollThumbTop, 0).tex(1, 0).color(192, 192, 192, 255).endVertex(); + buffer.pos(scrollBarLeft, scrollThumbTop, 0).tex(0, 0).color(192, 192, 192, 255).endVertex(); + tessellator.draw(); + } + + protected void drawSelectionBox(int mouseX, int mouseY, float partialTicks) { + int size = this.getSize(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + for (int index = 0; index < size; ++index) { + int rowTop = this.getRowTop(index); + int rowBottom = this.getRowBottom(index) - 4; + + if (rowTop > this.bottom || rowBottom < this.top) { + this.updateItemPos(index, this.getListLeft(), rowTop, partialTicks); + } + + if (this.showSelectionBox && this.isSelected(index)) { + int left = this.getListLeft(); + int right = this.getListRight(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.disableTexture2D(); + buffer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); + buffer.pos(left, rowBottom + 2, 0.0D).tex(0.0D, 1.0D).color(128, 128, 128, 255).endVertex(); + buffer.pos(right, rowBottom + 2, 0.0D).tex(1.0D, 1.0D).color(128, 128, 128, 255).endVertex(); + buffer.pos(right, rowTop - 2, 0.0D).tex(1.0D, 0.0D).color(128, 128, 128, 255).endVertex(); + buffer.pos(left, rowTop - 2, 0.0D).tex(0.0D, 0.0D).color(128, 128, 128, 255).endVertex(); + buffer.pos(left + 1, rowBottom + 1, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos(right - 1, rowBottom + 1, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos(right - 1, rowTop - 1, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 255).endVertex(); + buffer.pos(left + 1, rowTop - 1, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 255).endVertex(); + tessellator.draw(); + GlStateManager.enableTexture2D(); + } + + this.drawSlot(index, this.getListLeft(), rowTop, rowBottom - rowTop, mouseX, mouseY, partialTicks); + } + } + + @Deprecated + protected void drawSelectionBox(int contentLeft, int contentTop, int mouseXIn, int mouseYIn, float partialTicks) { + } + public void setAmountScrolled(int amountScrolled) { this.amountScrolled = (float)amountScrolled; this.bindAmountScrolled(); this.initialClickY = -2; } - public void updateSize(int width, int height, int top, int bottom) { + public void setWidth(int width) { this.width = width; + this.right = this.left + this.width; + } + + public void setHeight(int height) { this.height = height; - this.top = top; - this.bottom = bottom; + this.bottom = this.top + height; + } + + protected boolean isScrollBarVisible() { + return this.scrollBarVisible; + } + + protected int getListLeft() { + return this.width / 2 - this.getListWidth() / 2 + 2; + } + + protected int getListRight() { + return this.getListLeft() + this.getListWidth(); + } + + protected int getListTop() { + return this.top + 4 - (int)this.amountScrolled; } - /** - * Sets whether draw the top and bottom shadow. - */ - public void setDrawTopAndBottom(boolean shouldDraw) { - this.shouldDrawTopAndBottom = shouldDraw; + protected int getRowTop(int pIndex) { + return this.top + 4 - (int)this.amountScrolled + pIndex * this.slotHeight + this.headerPadding; } - /** - * Sets whether draw the shadowed dirt background, which scrolls with the entries. - */ - public void setDrawBackground(boolean shouldDraw) { - this.shouldDrawBackground = shouldDraw; + protected int getRowBottom(int pIndex) { + return this.getRowTop(pIndex) + this.slotHeight; } } diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index 237230fed..b06d37ce3 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -31,14 +31,12 @@ import java.net.MalformedURLException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; -import com.cleanroommc.common.CatalogueContainer; import com.cleanroommc.common.CleanroomContainer; import com.cleanroommc.common.MixinContainer; import com.cleanroommc.common.ConfigAnytimeContainer; @@ -379,7 +377,6 @@ private ModDiscoverer identifyMods(List additionalContainers) mods.add(new InjectedModContainer(new CleanroomContainer(), FMLSanityChecker.fmlLocation)); mods.add(new InjectedModContainer(new MixinContainer(), FMLSanityChecker.fmlLocation)); mods.add(new InjectedModContainer(new ConfigAnytimeContainer(), FMLSanityChecker.fmlLocation)); - mods.add(new InjectedModContainer(new CatalogueContainer(), FMLSanityChecker.fmlLocation)); for (String cont : injectedContainers) { diff --git a/src/main/resources/assets/forge/textures/gui/minecraft.png b/src/main/resources/assets/forge/textures/gui/minecraft.png new file mode 100644 index 0000000000000000000000000000000000000000..7fd8316e4fb6f438d683fb7c2b759266b6a2e2cb GIT binary patch literal 34429 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