diff --git a/src/main/java/io/github/prospector/modmenu/ModMenu.java b/src/main/java/io/github/prospector/modmenu/ModMenu.java index 69c195d7d..8d2e42bfb 100644 --- a/src/main/java/io/github/prospector/modmenu/ModMenu.java +++ b/src/main/java/io/github/prospector/modmenu/ModMenu.java @@ -6,7 +6,6 @@ import com.google.gson.GsonBuilder; import io.github.prospector.modmenu.api.ModMenuApi; import io.github.prospector.modmenu.config.ModMenuConfigManager; -import io.github.prospector.modmenu.util.HardcodedUtil; import net.fabricmc.api.ModInitializer; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; @@ -88,7 +87,6 @@ public void onInitialize() { }); configScreenFactories.put("minecraft", (screenBase -> new ScreenOptions(screenBase, OptionsPages.GENERAL))); Collection mods = FabricLoader.getInstance().getAllMods(); - HardcodedUtil.initializeHardcodings(); for (ModContainer mod : mods) { ModMetadata metadata = mod.getMetadata(); String id = metadata.getId(); @@ -129,8 +127,14 @@ public void onInitialize() { list.add(mod); }); } - } else { - HardcodedUtil.hardcodeModuleMetadata(mod, metadata, id); + } + + // Hardcode marking as library + if (id.equals("fabricloader") + || id.equals("fabric") + || id.equals("java") + || id.equals("mixinextras")) { + addLibraryMod(id); } } } diff --git a/src/main/java/io/github/prospector/modmenu/config/ModMenuConfig.java b/src/main/java/io/github/prospector/modmenu/config/ModMenuConfig.java index 128a3df5d..c0d4351b2 100644 --- a/src/main/java/io/github/prospector/modmenu/config/ModMenuConfig.java +++ b/src/main/java/io/github/prospector/modmenu/config/ModMenuConfig.java @@ -1,7 +1,6 @@ package io.github.prospector.modmenu.config; -import io.github.prospector.modmenu.util.HardcodedUtil; import net.fabricmc.loader.api.ModContainer; import net.minecraft.core.lang.I18n; @@ -30,8 +29,8 @@ public Sorting getSorting() { } public enum Sorting { - ASCENDING(Comparator.comparing(modContainer -> HardcodedUtil.formatFabricModuleName(modContainer.getMetadata().getName())), "modmenu.sorting.ascending"), - DECENDING(ASCENDING.getComparator().reversed(), "modmenu.sorting.decending"); + ASCENDING(Comparator.comparing(modContainer -> modContainer.getMetadata().getName(), String.CASE_INSENSITIVE_ORDER),"modmenu.sorting.ascending"), + DESCENDING(ASCENDING.getComparator().reversed(),"modmenu.sorting.descending"); final Comparator comparator; final String key; diff --git a/src/main/java/io/github/prospector/modmenu/gui/DescriptionListWidget.java b/src/main/java/io/github/prospector/modmenu/gui/DescriptionListWidget.java index 24d758deb..12e2065d0 100644 --- a/src/main/java/io/github/prospector/modmenu/gui/DescriptionListWidget.java +++ b/src/main/java/io/github/prospector/modmenu/gui/DescriptionListWidget.java @@ -1,14 +1,18 @@ package io.github.prospector.modmenu.gui; -import io.github.prospector.modmenu.util.HardcodedUtil; import io.github.prospector.modmenu.util.RenderUtils; +import net.fabricmc.loader.api.metadata.ContactInformation; import net.fabricmc.loader.api.metadata.Person; import net.minecraft.client.Minecraft; import net.minecraft.client.render.font.FontRenderer; +import net.minecraft.client.util.helper.UrlHelper; import net.minecraft.core.lang.I18n; +import net.minecraft.core.net.command.TextFormatting; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; public class DescriptionListWidget extends EntryListWidget { private final ModListScreen parent; @@ -36,6 +40,37 @@ protected int getScrollbarPosition() { return this.width - 6 + left; } + protected class LinkEntry extends DescriptionEntry { + private final String link; + private final int indent; + private int lastRenderX; + private int lastRenderY; + + public LinkEntry(String text, String link, int indent) { + super(text); + this.link = link; + this.indent = indent; + } + + @Override + public void render(int index, int y, int x, int itemWidth, int itemHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + this.lastRenderX = x + indent; + this.lastRenderY = y; + + String formattedText = TextFormatting.formatted(text, TextFormatting.BLUE, TextFormatting.UNDERLINE); + this.drawStringShadow(this.fontRenderer, formattedText, lastRenderX, lastRenderY, 0xFFFFFF); + } + + public void mouseClicked(int mouseX, int mouseY, int button) { + if (button == 0) { + int textWidth = fontRenderer.stringWidth(text); + if (mouseX >= lastRenderX && mouseX <= lastRenderX + textWidth && mouseY >= lastRenderY && mouseY <= lastRenderY + 12) { + UrlHelper.openURL(link); + } + } + } + } + @Override public void render(int mouseX, int mouseY, float delta) { I18n i18n = I18n.getInstance(); @@ -44,19 +79,40 @@ public void render(int mouseX, int mouseY, float delta) { lastSelected = selectedEntry; clearEntries(); setScrollAmount(-Double.MAX_VALUE); - String description = lastSelected.getMetadata().getDescription(); - String id = lastSelected.getMetadata().getId(); - Collection authors = lastSelected.getMetadata().getAuthors(); - Collection contributors = lastSelected.getMetadata().getContributors(); - Collection licenses = lastSelected.getMetadata().getLicense(); - if (description.isEmpty() && HardcodedUtil.getHardcodedDescriptions().containsKey(id)) { - description = HardcodedUtil.getHardcodedDescription(id); + String id = selectedEntry.getMetadata().getId(); + String descKey = "modmenu.descriptionTranslation." + id; + String translatedDesc = i18n.translateKey(descKey); + + String description; + if (!translatedDesc.equals(descKey)) { + description = translatedDesc; + } else { + description = selectedEntry.getMetadata().getDescription(); } + + if ("java".equals(id)) { + String vendor = System.getProperty("java.vendor", "Unknown Vendor"); + String javaDist = i18n.translateKeyAndFormat("modmenu.javaDistributionName", vendor); + description = (description != null ? description : "") + "\n" + javaDist; + } + if (lastSelected != null && description != null && !description.isEmpty()) { - for (String line : RenderUtils.INSTANCE.wrapStringToWidthAsList(textRenderer, description.replaceAll("\n", "\n\n"), getRowWidth())) { - children().add(new DescriptionEntry(line)); + String[] paragraphs = description.split("\r?\n"); + for (String paragraph : paragraphs) { + if (paragraph.isEmpty()) { + children().add(new DescriptionEntry("")); + continue; + } + for (String line : RenderUtils.INSTANCE.wrapStringToWidthAsList(textRenderer, paragraph, getRowWidth())) { + children().add(new DescriptionEntry(line)); + } } } + + Collection authors = selectedEntry.getMetadata().getAuthors(); + Collection contributors = selectedEntry.getMetadata().getContributors(); + Collection licenses = selectedEntry.getMetadata().getLicense(); + if (!authors.isEmpty()) { if (!children().isEmpty()) children().add(new DescriptionEntry("")); children().add(new DescriptionEntry(i18n.translateKey("modmenu.authors"))); @@ -64,6 +120,7 @@ public void render(int mouseX, int mouseY, float delta) { children().add(new DescriptionEntry(" " + person.getName())); } } + if (!contributors.isEmpty()) { if (!children().isEmpty()) children().add(new DescriptionEntry("")); children().add(new DescriptionEntry(i18n.translateKey("modmenu.contributors"))); @@ -71,6 +128,36 @@ public void render(int mouseX, int mouseY, float delta) { children().add(new DescriptionEntry(" " + person.getName())); } } + + ContactInformation contact = selectedEntry.getMetadata().getContact(); + Map links = new HashMap<>(contact.asMap()); + + links.remove("homepage"); + links.remove("issues"); + + if (!links.isEmpty()) { + children().add(new DescriptionEntry("")); + + for (String line : RenderUtils.INSTANCE.wrapStringToWidthAsList(textRenderer, i18n.translateKey("modmenu.links"), getRowWidth())) { + children().add(new DescriptionEntry(line)); + } + + links.forEach((key, value) -> { + int indent = 8; + String translationKey = "modmenu." + key; + String translatedKey = i18n.translateKey(translationKey); + + if (translatedKey.equals(translationKey)) { + translatedKey = Character.toUpperCase(key.charAt(0)) + key.substring(1); + } + + for (String line : RenderUtils.INSTANCE.wrapStringToWidthAsList(textRenderer, translatedKey, getRowWidth() - 16)) { + children().add(new LinkEntry(line, value, indent)); + indent = 16; + } + }); + } + if (!licenses.isEmpty()) { if (!children().isEmpty()) children().add(new DescriptionEntry("")); children().add(new DescriptionEntry(i18n.translateKey("modmenu.licenses"))); @@ -82,6 +169,16 @@ public void render(int mouseX, int mouseY, float delta) { super.render(mouseX, mouseY, delta); } + @Override + public void mouseClicked(int mouseX, int mouseY, int button) { + super.mouseClicked(mouseX, mouseY, button); + for (DescriptionEntry entry : children()) { + if (entry instanceof LinkEntry) { + entry.mouseClicked(mouseX, mouseY, button); + } + } + } + @Override protected void renderHoleBackground(int y1, int y2, int startAlpha, int endAlpha) { // Awful hack but it makes the background "seamless" diff --git a/src/main/java/io/github/prospector/modmenu/gui/EntryListWidget.java b/src/main/java/io/github/prospector/modmenu/gui/EntryListWidget.java index 4dfdb5788..09fc45c0f 100644 --- a/src/main/java/io/github/prospector/modmenu/gui/EntryListWidget.java +++ b/src/main/java/io/github/prospector/modmenu/gui/EntryListWidget.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Objects; -@SuppressWarnings({"unchecked", "unused"}) +@SuppressWarnings({"unused"}) @Environment(EnvType.CLIENT) public abstract class EntryListWidget> extends Screen { protected static final int DRAG_OUTSIDE = -2; @@ -39,7 +39,7 @@ public abstract class EntryListWidget> extend protected boolean renderSelection = true; protected boolean renderHeader; protected int headerHeight; - private boolean scrolling; + protected boolean scrolling; private E selected; private E focused; @@ -168,7 +168,6 @@ protected void renderDecorations(int i, int j) { } public void render(int i, int j, float f) { - oldY = j; this.renderBackground(); int k = this.getScrollbarPosition(); int l = k + 6; @@ -321,25 +320,25 @@ protected int getScrollbarPosition() { return this.width / 2 + 124; } - @Override + @Override public void mouseClicked(int d, int e, int i) { this.updateScrollingState(d, e, i); if (this.isMouseOver(d, e)) { E entry = this.getEntryAtPosition(d, e); if (entry != null) { - if (entry.list.getFocused() != null) { - if (!entry.list.getFocused().equals(entry)) { - this.focused = entry; - this.selected = entry; - this.dragging = true; - super.mouseClicked(d, e, i); - } - } else { - this.focused = entry; - this.selected = entry; - this.dragging = true; - super.mouseClicked(d, e, i); - } + if (entry.list.getFocused() != null) { + if (!entry.list.getFocused().equals(entry)) { + this.focused = entry; + this.selected = entry; + this.dragging = true; + super.mouseClicked(d, e, i); + } + } else { + this.focused = entry; + this.selected = entry; + this.dragging = true; + super.mouseClicked(d, e, i); + } } else if (i == 0) { this.clickedHeader((int)(d - (double)(this.left + this.width / 2 - this.getRowWidth() / 2)), (int)(e - (double)this.top) + (int)this.getScrollAmount() - 4); } @@ -353,14 +352,12 @@ public boolean mouseReleased(double d, double e, int i) { return false; } - double oldY = -1; public boolean mouseDragged(double mouseX, double mouseY, int mouseButton, double mouseDX, double mouseDY) { - if (mouseButton == 0 && isMouseOver(mouseX, mouseY)) { - setScrollAmount(getScrollAmount() - mouseY + oldY); + if (this.scrolling) { + setScrollAmount(getScrollAmount() - mouseDY); return true; - } else { - return false; } + return false; } public boolean mouseScrolled(double d, double e, double f) { @@ -578,6 +575,5 @@ public boolean charTyped(char c, int i) { public boolean changeFocus(boolean bl) { return false; } - } } diff --git a/src/main/java/io/github/prospector/modmenu/gui/ModListEntry.java b/src/main/java/io/github/prospector/modmenu/gui/ModListEntry.java index ccc28f42f..2486ae2f9 100644 --- a/src/main/java/io/github/prospector/modmenu/gui/ModListEntry.java +++ b/src/main/java/io/github/prospector/modmenu/gui/ModListEntry.java @@ -3,7 +3,6 @@ import io.github.prospector.modmenu.ModMenu; import io.github.prospector.modmenu.util.BadgeRenderer; -import io.github.prospector.modmenu.util.HardcodedUtil; import io.github.prospector.modmenu.util.RenderUtils; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; @@ -14,6 +13,7 @@ import net.minecraft.client.render.renderer.Shaders; import net.minecraft.client.render.renderer.State; import net.minecraft.client.render.tessellator.TessellatorGeneral; +import net.minecraft.core.lang.I18n; import org.lwjgl.opengl.GL11; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +44,7 @@ public ModListEntry(Minecraft mc, ModContainer container, ModListWidget list) { @Override public void render(int index, int y, int x, int rowWidth, int rowHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + I18n i18n = I18n.getInstance(); x += getXOffset(); rowWidth -= getXOffset(); @@ -52,22 +53,59 @@ public void render(int index, int y, int x, int rowWidth, int rowHeight, int mou GLRenderer.setColor4f(1, 1, 1, 1); // We LOVE random color calls this.bindIconTexture(); internalRender(y, x); - String name = metadata.getName(); - if (name.equals("Minecraft")){ // BAD CODE - name = "Better than Adventure"; + + String id = metadata.getId(); + String translationKey = "modmenu.nameTranslation." + id; + String translatedName = i18n.translateKey(translationKey); + String name; + if (!translatedName.equals(translationKey)) { + name = translatedName; + } else { + name = metadata.getName(); } - name = HardcodedUtil.formatFabricModuleName(name); String trimmedName = name; int maxNameWidth = rowWidth - 32 - 3; FontRenderer font = this.fontRenderer; trimmedName = ModListScreen.getString(fontRenderer, name, trimmedName, maxNameWidth); this.drawStringNoShadow(font, trimmedName, x + 32 + 3, y + 1, 0xFFFFFF); new BadgeRenderer(client, x + 32 + 3 + font.stringWidth(name) + 2, y, x + rowWidth, container, list.getParent()).draw(mouseX, mouseY); - String description = metadata.getDescription(); - if (description.isEmpty() && HardcodedUtil.getHardcodedDescriptions().containsKey(metadata.getId())) { - description = HardcodedUtil.getHardcodedDescription(metadata.getId()); + + String descKey = "modmenu.descriptionTranslation." + id; + String translatedDesc = i18n.translateKey(descKey); + String description; + if (!translatedDesc.equals(descKey)) { + description = translatedDesc; + } else { + description = metadata.getDescription(); + } + + if ("java".equals(id)) { + String vendor = System.getProperty("java.vendor", "Unknown Vendor"); + String javaDist = i18n.translateKeyAndFormat("modmenu.javaDistributionName", vendor); + description = (description != null ? description : "") + "\n" + javaDist; + } + + if (description != null) { + int textX = x + 32 + 3 + 4; + int textY = y + 9 + 2; + int maxWidth = rowWidth - 32 - 7; + int maxLines = 2; + int currentLine = 0; + + font = this.fontRenderer; + String[] paragraphs = description.split("\r?\n"); + + for (String paragraph : paragraphs) { + if (currentLine >= maxLines) break; + + for (String line : RenderUtils.INSTANCE.wrapStringToWidthAsList(font, paragraph, maxWidth)) { + if (currentLine >= maxLines) break; + + this.drawStringNoShadow(font, line, textX, textY + (currentLine * 9), 0x808080); + currentLine++; + } + } } - RenderUtils.INSTANCE.drawWrappedString(this, description, (x + 32 + 3 + 4), (y + 9 + 2), rowWidth - 32 - 7, 2, 0x808080); GLRenderer.popFrame(); } @@ -97,9 +135,7 @@ private BufferedImage createIcon() { } if (!Files.exists(path)) { ModContainer modMenu = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID).orElseThrow(IllegalAccessError::new); - if (HardcodedUtil.getFabricMods().contains(metadata.getId())) { - path = modMenu.getPath("assets/" + ModMenu.MOD_ID + "/fabric_icon.png"); - } else if (metadata.getId().equals("minecraft")) { + if (metadata.getId().equals("minecraft")) { path = modMenu.getPath("assets/" + ModMenu.MOD_ID + "/mc_icon.png"); } else if (metadata.getId().equals("java")) { path = modMenu.getPath("assets/" + ModMenu.MOD_ID + "/java_icon.png"); diff --git a/src/main/java/io/github/prospector/modmenu/gui/ModListScreen.java b/src/main/java/io/github/prospector/modmenu/gui/ModListScreen.java index c5ce372de..70ea1a4fb 100644 --- a/src/main/java/io/github/prospector/modmenu/gui/ModListScreen.java +++ b/src/main/java/io/github/prospector/modmenu/gui/ModListScreen.java @@ -4,7 +4,6 @@ import io.github.prospector.modmenu.config.ModMenuConfigManager; import io.github.prospector.modmenu.util.BadgeRenderer; import io.github.prospector.modmenu.util.ButtonUtil; -import io.github.prospector.modmenu.util.HardcodedUtil; import io.github.prospector.modmenu.util.RenderUtils; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.metadata.ModMetadata; @@ -17,9 +16,10 @@ import net.minecraft.client.render.renderer.Shaders; import net.minecraft.client.render.renderer.State; import net.minecraft.client.render.tessellator.TessellatorGeneral; +import net.minecraft.client.util.helper.FileOpener; +import net.minecraft.client.util.helper.UrlHelper; import net.minecraft.core.Global; import net.minecraft.core.lang.I18n; -import org.lwjgl.Sys; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL41; @@ -28,7 +28,6 @@ import org.spongepowered.include.com.google.common.base.Joiner; import java.io.File; -import java.net.MalformedURLException; import java.text.NumberFormat; import java.util.*; @@ -63,10 +62,12 @@ public class ModListScreen extends Screen { private static final int TOGGLE_SHOW_LIBRARIES_BUTTON_ID = 5; private static final int MODS_FOLDER_BUTTON_ID = 6; private static final int DONE_BUTTON_ID = 7; + + I18n i18n = I18n.getInstance(); public ModListScreen(Screen previousGui) { this.parent = previousGui; - this.textTitle = I18n.getInstance().translateKey("modmenu.title"); + this.textTitle = i18n.translateKey("modmenu.title"); } public void updateEvents() { @@ -93,7 +94,6 @@ public void tick() { @Override public void init() { - I18n i18n = I18n.getInstance(); Keyboard.enableRepeatEvents(true); FontRenderer font = this.fontRenderer; paneY = 48; @@ -186,8 +186,8 @@ public void drawButton(Minecraft mc, int mouseX, int mouseY) { this.buttons.add(configureButton); this.buttons.add(websiteButton); this.buttons.add(issuesButton); - this.buttons.add(ButtonUtil.createButton(MODS_FOLDER_BUTTON_ID, this.width / 2 - 154, this.height - 28, 150, 20, "Open Mods Folder")); - this.buttons.add(ButtonUtil.createButton(DONE_BUTTON_ID, this.width / 2 + 4, this.height - 28, 150, 20, "Done")); + this.buttons.add(ButtonUtil.createButton(MODS_FOLDER_BUTTON_ID, this.width / 2 - 154, this.height - 28, 150, 20, i18n.translateKey("modmenu.modsFolder"))); + this.buttons.add(ButtonUtil.createButton(DONE_BUTTON_ID, this.width / 2 + 4, this.height - 28, 150, 20, i18n.translateKey("modmenu.done"))); this.searchBox.setFocused(true); init = true; @@ -207,13 +207,11 @@ protected void buttonClicked(ButtonElement button) { break; } case WEBSITE_BUTTON_ID: { - final ModMetadata metadata = Objects.requireNonNull(selected).getMetadata(); - metadata.getContact().get("homepage").ifPresent(Sys::openURL); + Objects.requireNonNull(selected).getMetadata().getContact().get("homepage").ifPresent(UrlHelper::openURL); break; } case ISSUES_BUTTON_ID: { - final ModMetadata metadata = Objects.requireNonNull(selected).getMetadata(); - metadata.getContact().get("issues").ifPresent(Sys::openURL); + Objects.requireNonNull(selected).getMetadata().getContact().get("issues").ifPresent(UrlHelper::openURL); break; } case TOGGLE_FILTER_OPTIONS_BUTTON_ID: { @@ -232,10 +230,13 @@ protected void buttonClicked(ButtonElement button) { } case MODS_FOLDER_BUTTON_ID: { File modsFolder = new File(FabricLoader.getInstance().getGameDir().toFile(), "mods"); + if (!modsFolder.exists()) { + modsFolder.mkdirs(); + } try { - Sys.openURL(modsFolder.toURI().toURL().toString()); - } catch (MalformedURLException e) { - LOGGER.error("Malformed mods folder URL", e); + FileOpener.open(modsFolder); + } catch (Exception e) { + LOGGER.error("Failed to open mods folder", e); } break; } @@ -253,9 +254,9 @@ public ModListWidget getModList() { @Override public void keyPressed(char char_1, int int_1, int mouseX, int mouseY) { this.searchBox.textboxKeyTyped(char_1, int_1); - if (int_1 == 1) { - this.mc.displayScreen(this.parent); - } + if (int_1 == 1) { + this.mc.displayScreen(this.parent); + } modList.keyPressed(int_1, 0, 0); descriptionListWidget.keyPressed(int_1, 0, 0); } @@ -275,12 +276,21 @@ public void mouseReleased(int mouseX, int mouseY, int mouseButton) { descriptionListWidget.mouseReleased(mouseX, mouseY, mouseButton); } } - + @Override public void render(int mouseX, int mouseY, float delta) { - I18n i18n = I18n.getInstance(); - int mouseDX = Mouse.getEventDX() * this.width / this.mc.resolution.getScaledWidthScreenCoords(); // field_6326_c - int mouseDY = this.height - Mouse.getEventDY() * this.height / this.mc.resolution.getScaledHeightScreenCoords() - 1; // field_6325_d + int dWheel = Mouse.getDWheel(); + if (dWheel != 0) { + double scrollDelta = dWheel > 0 ? 1.0 : -1.0; + if (this.modList != null && this.modList.isMouseOver(mouseX, mouseY)) { + this.modList.mouseScrolled(mouseX, mouseY, scrollDelta); + } else if (this.descriptionListWidget != null && this.descriptionListWidget.isMouseOver(mouseX, mouseY)) { + this.descriptionListWidget.mouseScrolled(mouseX, mouseY, scrollDelta); + } + } + + int mouseDX = Mouse.getDX() * this.width / this.mc.resolution.getScaledWidthScreenCoords(); // field_6326_c + int mouseDY = Mouse.getDY() * this.height / this.mc.resolution.getScaledHeightScreenCoords(); // field_6325_d for (int button = 0; button < Mouse.getButtonCount(); button++) { if (Mouse.isButtonDown(button)) { modList.mouseDragged(mouseX, mouseY, button, mouseDX, mouseDY); @@ -318,18 +328,23 @@ public void render(int mouseX, int mouseY, float delta) { GLRenderer.setColor4f(1, 1, 1, 1); this.selected.bindIconTexture(); - ModListEntry.internalRender(paneY, x); - int lineSpacing = 9 + 1; + ModListEntry.internalRender(paneY, x); + int lineSpacing = 9 + 1; int imageOffset = 36; - String name = metadata.getName(); - if (name.equals("Minecraft")){ // BAD CODE - name = "Better than Adventure"; + String id = metadata.getId(); + String translationKey = "modmenu.nameTranslation." + id; + String translatedName = i18n.translateKey(translationKey); + String name; + if (!translatedName.equals(translationKey)) { + name = translatedName; + } else { + name = metadata.getName(); } - name = HardcodedUtil.formatFabricModuleName(name); + String trimmedName = name; int maxNameWidth = this.width - (x + imageOffset); - trimmedName = getString(font, name, trimmedName, maxNameWidth); - this.drawStringNoShadow(font, trimmedName, x + imageOffset, paneY + 1, 0xFFFFFF); + trimmedName = getString(font, name, trimmedName, maxNameWidth); + this.drawStringNoShadow(font, trimmedName, x + imageOffset, paneY + 1, 0xFFFFFF); if (mouseX > x + imageOffset && mouseY > paneY + 1 && mouseY < paneY + 1 + 9 && mouseX < x + imageOffset + fontRenderer.stringWidth(trimmedName)) { setTooltip(i18n.translateKeyAndFormat("modmenu.modIdToolTip", metadata.getId())); } @@ -338,21 +353,18 @@ public void render(int mouseX, int mouseY, float delta) { init = false; } badgeRenderer.draw(mouseX, mouseY); - String versionString; - if (metadata.getName().equals("Minecraft")){ // BAD CODE - versionString = Global.VERSION; - } else { - versionString = metadata.getVersion().getFriendlyString(); - } + String versionString = metadata.getId().equals("minecraft") + ? Global.VERSION + : metadata.getVersion().getFriendlyString(); this.drawStringNoShadow(font, "v" + versionString, x + imageOffset, paneY + 2 + lineSpacing, 0x808080); String authors; List names = new ArrayList<>(); metadata.getAuthors().stream() - .filter(Objects::nonNull) - .map(Person::getName) - .filter(Objects::nonNull) - .forEach(names::add); + .filter(Objects::nonNull) + .map(Person::getName) + .filter(Objects::nonNull) + .forEach(names::add); if (!names.isEmpty()) { if (names.size() > 1) { @@ -372,19 +384,19 @@ public void render(int mouseX, int mouseY, float delta) { GLRenderer.popFrame(); } - static String getString(FontRenderer fontRenderer, String name, String trimmedName, int maxNameWidth) { - if (fontRenderer.stringWidth(name) > maxNameWidth) { - int maxWidth = maxNameWidth - fontRenderer.stringWidth("..."); - trimmedName = ""; - while (fontRenderer.stringWidth(trimmedName) < maxWidth && trimmedName.length() < name.length()) { - trimmedName += name.charAt(trimmedName.length()); - } - trimmedName = trimmedName.isEmpty() ? "..." : trimmedName.substring(0, trimmedName.length() - 1) + "..."; - } - return trimmedName; - } - - public void overlayBackground(int x1, int y1, int x2, int y2, int red, int green, int blue, int startAlpha, int endAlpha) { + static String getString(FontRenderer fontRenderer, String name, String trimmedName, int maxNameWidth) { + if (fontRenderer.stringWidth(name) > maxNameWidth) { + int maxWidth = maxNameWidth - fontRenderer.stringWidth("..."); + trimmedName = ""; + while (fontRenderer.stringWidth(trimmedName) < maxWidth && trimmedName.length() < name.length()) { + trimmedName += name.charAt(trimmedName.length()); + } + trimmedName = trimmedName.isEmpty() ? "..." : trimmedName.substring(0, trimmedName.length() - 1) + "..."; + } + return trimmedName; + } + + public void overlayBackground(int x1, int y1, int x2, int y2, int red, int green, int blue, int startAlpha, int endAlpha) { GLRenderer.pushFrame(); GLRenderer.setShader(Shaders.INTERFACE); diff --git a/src/main/java/io/github/prospector/modmenu/gui/ModListWidget.java b/src/main/java/io/github/prospector/modmenu/gui/ModListWidget.java index 0b2dbc52f..eeeee2339 100644 --- a/src/main/java/io/github/prospector/modmenu/gui/ModListWidget.java +++ b/src/main/java/io/github/prospector/modmenu/gui/ModListWidget.java @@ -239,25 +239,34 @@ public void mouseClicked(int double_1, int double_2, int int_1) { if (this.isMouseOver(double_1, double_2)) { ModListEntry entry = this.getEntryAtPos(double_1, double_2); if (entry != null) { - if (entry.list.getFocused() != null) { - if (!entry.list.getFocused().equals(entry)) { - this.setFocused(entry); - this.setSelected(entry); - this.setDragging(true); - super.mouseClicked(double_1, double_2, int_1); - } - } else { - this.setFocused(entry); - this.setSelected(entry); - this.setDragging(true); - super.mouseClicked(double_1, double_2, int_1); - } + if (entry.list.getFocused() != null) { + if (!entry.list.getFocused().equals(entry)) { + this.setFocused(entry); + this.setSelected(entry); + this.setDragging(true); + super.mouseClicked(double_1, double_2, int_1); + } + } else { + this.setFocused(entry); + this.setSelected(entry); + this.setDragging(true); + super.mouseClicked(double_1, double_2, int_1); + } } else if (int_1 == 0) { this.clickedHeader((int) (double_1 - (double) (this.left + this.width / 2 - this.getRowWidth() / 2)), (int) (double_2 - (double) this.top) + (int) this.getScrollAmount() - 4); } } } + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollDelta) { + if (this.isMouseOver(mouseX, mouseY)) { + setScrollAmount(getScrollAmount() - scrollDelta * 20.0); + return true; + } + return false; + } + public final ModListEntry getEntryAtPos(double x, double y) { int int_5 = MathHelper.floor(y - (double) this.top) - this.headerHeight + (int) this.getScrollAmount() - 4; // convertToBlockCoord int index = int_5 / this.itemHeight; diff --git a/src/main/java/io/github/prospector/modmenu/impl/ModMenuApiImpl.java b/src/main/java/io/github/prospector/modmenu/impl/ModMenuApiImpl.java deleted file mode 100644 index 4496da662..000000000 --- a/src/main/java/io/github/prospector/modmenu/impl/ModMenuApiImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.prospector.modmenu.impl; - - -import io.github.prospector.modmenu.api.ModMenuApi; -import io.github.prospector.modmenu.util.TriConsumer; - -public class ModMenuApiImpl implements ModMenuApi { - @Override - public String getModId() { - return "modmenu"; - } - - @Override - public void attachCustomBadges(TriConsumer consumer) { - consumer.accept("Mod Menu", 0xff7a2b7c, 0xff510d54); - } -} diff --git a/src/main/java/io/github/prospector/modmenu/mixin/MixinGuiIngameMenu.java b/src/main/java/io/github/prospector/modmenu/mixin/MixinGuiIngameMenu.java index 50678bb64..89672d307 100644 --- a/src/main/java/io/github/prospector/modmenu/mixin/MixinGuiIngameMenu.java +++ b/src/main/java/io/github/prospector/modmenu/mixin/MixinGuiIngameMenu.java @@ -15,7 +15,6 @@ @Mixin(value = ScreenPause.class, remap = false) public class MixinGuiIngameMenu extends Screen { - @SuppressWarnings("unchecked") @Inject(at = @At("RETURN"), method = "init") public void modmenu$drawMenuButton(CallbackInfo info) { I18n i18n = I18n.getInstance(); diff --git a/src/main/java/io/github/prospector/modmenu/util/BadgeRenderer.java b/src/main/java/io/github/prospector/modmenu/util/BadgeRenderer.java index a19b76738..97b0bbe57 100644 --- a/src/main/java/io/github/prospector/modmenu/util/BadgeRenderer.java +++ b/src/main/java/io/github/prospector/modmenu/util/BadgeRenderer.java @@ -33,19 +33,19 @@ public void draw(int mouseX, int mouseY) { this.badgeX = startX; this.badgeY = startY; if (ModMenu.LIBRARY_MODS.contains(metadata.getId())) { - drawBadge(i18n.translateKey("modmenu.library"), 0x8810d098, 0x88046146, mouseX, mouseY); + drawBadge(i18n.translateKey("modmenu.badge.library"), 0x8810d098, 0x88046146, mouseX, mouseY); } if (ModMenu.CLIENTSIDE_MODS.contains(metadata.getId())) { - drawBadge(i18n.translateKey("modmenu.clientsideOnly"), 0x884383E3, 0x880E4699, mouseX, mouseY); + drawBadge(i18n.translateKey("modmenu.badge.clientsideOnly"), 0x884383E3, 0x880E4699, mouseX, mouseY); } if (ModMenu.DEPRECATED_MODS.contains(metadata.getId())) { - drawBadge(i18n.translateKey("modmenu.deprecated"), 0xFF841426, 0xFF530C17, mouseX, mouseY); + drawBadge(i18n.translateKey("modmenu.badge.deprecated"), 0xFF841426, 0xFF530C17, mouseX, mouseY); } if (ModMenu.PATCHWORK_FORGE_MODS.contains(metadata.getId())) { - drawBadge(i18n.translateKey("modmenu.forge"), 0x887C89A3, 0x88202C43, mouseX, mouseY); + drawBadge(i18n.translateKey("modmenu.badge.forge"), 0x887C89A3, 0x88202C43, mouseX, mouseY); } if (metadata.getId().equals("minecraft")) { - drawBadge(i18n.translateKey("modmenu.minecraft"), 0x88BCBCBC, 0x88535353, mouseX, mouseY); + drawBadge(i18n.translateKey("modmenu.badge.minecraft"), 0x88BCBCBC, 0x88535353, mouseX, mouseY); } if (ModMenu.CUSTOM_BADGE_MODS.containsKey(metadata.getId())) { Map> map = ModMenu.CUSTOM_BADGE_MODS.get(metadata.getId()); diff --git a/src/main/java/io/github/prospector/modmenu/util/HardcodedUtil.java b/src/main/java/io/github/prospector/modmenu/util/HardcodedUtil.java deleted file mode 100644 index ca10db64e..000000000 --- a/src/main/java/io/github/prospector/modmenu/util/HardcodedUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -package io.github.prospector.modmenu.util; - - -import io.github.prospector.modmenu.ModMenu; -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; -import net.fabricmc.loader.api.metadata.ModMetadata; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class HardcodedUtil { - private static final Pattern FABRIC_PATTERN = Pattern.compile("^fabric-.*(-v\\d+)$"); - private static final Set FABRIC_MODS = new HashSet<>(); - private static final HashMap HARDCODED_DESCRIPTIONS = new HashMap<>(); - - public static void initializeHardcodings() { - HARDCODED_DESCRIPTIONS.put("minecraft", new Random().nextInt(1000) == 0 ? "The based game." : "The base game."); - } - - public static void hardcodeModuleMetadata(ModContainer mod, ModMetadata metadata, String id) { - Matcher matcher = FABRIC_PATTERN.matcher(id); - if (matcher.matches() || id.equals("fabric-api-base") || id.equals("fabric-renderer-indigo")) { - FABRIC_MODS.add(id); - if (FabricLoader.getInstance().isModLoaded("fabric")) { - Optional parent = FabricLoader.getInstance().getModContainer("fabric"); - parent.ifPresent(modContainer -> { - final List list = ModMenu.PARENT_MAP.computeIfAbsent(modContainer, k -> new ArrayList<>()); - list.add(mod); - });; - } - ModMenu.addLibraryMod(id); - if (id.equals("fabric-keybindings-v0") || id.equals("fabric-models-v0") || id.equals("fabric-renderer-api-v1") || id.equals("fabric-renderer-indigo") || id.equals("fabric-rendering-fluids-v1") || id.equals("fabric-rendering-v0") || id.equals("fabric-textures-v0")) { - ModMenu.CLIENTSIDE_MODS.add(id); - } - } - if (id.equals("fabricloader") || id.equals("fabric") || metadata.getName().endsWith(" API")) { - ModMenu.addLibraryMod(id); - } - } - - public static String formatFabricModuleName(String name) { - Matcher matcher = FABRIC_PATTERN.matcher(name); - if (matcher.matches() || name.equals("fabric-renderer-indigo") || name.equals("fabric-api-base")) { - if (matcher.matches()) { - String v = matcher.group(1); - name = capitalize(name.replace(v, "").replace("-", " ")); - name = name + " (" + v.replace("-", "") + ")"; - } else { - name = capitalize(name.replace("-", " ")); - } - name = name.replace("Api", "API"); - name = name.replace("Blockentity", "BlockEntity"); - } - return name; - } - - private static String capitalize(String str) { - StringBuilder sb = new StringBuilder(); - boolean capitalizeNext = true; - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - if (capitalizeNext) - c = Character.toTitleCase(c); - capitalizeNext = Character.isWhitespace(c); - sb.append(c); - } - return sb.toString(); - } - - public static String getHardcodedDescription(String id) { - return HARDCODED_DESCRIPTIONS.getOrDefault(id, ""); - } - - public static Set getFabricMods() { - return FABRIC_MODS; - } - - public static HashMap getHardcodedDescriptions() { - return HARDCODED_DESCRIPTIONS; - } -} diff --git a/src/main/java/io/github/prospector/modmenu/util/ModListSearch.java b/src/main/java/io/github/prospector/modmenu/util/ModListSearch.java index a21e9e567..d9dd0f396 100644 --- a/src/main/java/io/github/prospector/modmenu/util/ModListSearch.java +++ b/src/main/java/io/github/prospector/modmenu/util/ModListSearch.java @@ -6,6 +6,7 @@ import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.metadata.ModMetadata; import net.fabricmc.loader.api.metadata.Person; +import net.minecraft.core.lang.I18n; import java.util.List; import java.util.Locale; @@ -23,23 +24,34 @@ public static List search(ModListScreen screen, String query, List return candidates; } return candidates.stream() - .filter(modContainer -> passesFilters(screen, modContainer, query.toLowerCase(Locale.ROOT))) - .collect(Collectors.toList()); + .filter(modContainer -> passesFilters(screen, modContainer, query.toLowerCase(Locale.ROOT))) + .collect(Collectors.toList()); } private static boolean passesFilters(ModListScreen screen, ModContainer container, String query) { ModMetadata metadata = container.getMetadata(); String modId = metadata.getId(); - //Some basic search, could do with something more advanced but this will do for now - if (HardcodedUtil.formatFabricModuleName(metadata.getName()).toLowerCase(Locale.ROOT).contains(query) //Search mod name - || modId.toLowerCase(Locale.ROOT).contains(query) // Search mod name - || authorMatches(container, query) //Search via author - || (ModMenu.LIBRARY_MODS.contains(modId) && "api library".contains(query)) //Search for lib mods - || ("clientside".contains(query) && ModMenu.CLIENTSIDE_MODS.contains(modId)) //Search for clientside mods - || ("deprecated".contains(query) && ModMenu.DEPRECATED_MODS.contains(modId)) //Search for clientside mods - || ("configurations configs configures configurable".contains(query) && ModMenu.hasConfigScreenFactory(modId)) //Search for mods that can be configured + I18n i18n = I18n.getInstance(); + + String nameTranslation = "modmenu.nameTranslation." + modId; + String translatedName = i18n.translateKey(nameTranslation); + String modName = !translatedName.equals(nameTranslation) ? translatedName : metadata.getName(); + + String descriptionTranslation = "modmenu.descriptionTranslation." + modId; + String translatedDescription = i18n.translateKey(descriptionTranslation); + String modDescription = !translatedDescription.equals(descriptionTranslation) ? translatedDescription : metadata.getDescription(); + + if (modName.toLowerCase(Locale.ROOT).contains(query) // Search mod name + || modDescription.toLowerCase(Locale.ROOT).contains(query) // Search mod description + || modId.toLowerCase(Locale.ROOT).contains(query) // Search mod id + || authorMatches(container, query) // Search via author + || (ModMenu.LIBRARY_MODS.contains(modId) && i18n.translateKey("modmenu.searchTerms.library").toLowerCase(Locale.ROOT).contains(query)) // Search for lib mods + || (ModMenu.CLIENTSIDE_MODS.contains(modId) && i18n.translateKey("modmenu.searchTerms.clientside").toLowerCase(Locale.ROOT).contains(query)) // Search for clientside mods + || (ModMenu.DEPRECATED_MODS.contains(modId) && i18n.translateKey("modmenu.searchTerms.deprecated").toLowerCase(Locale.ROOT).contains(query)) // Search for deprecated mods + || (ModMenu.PATCHWORK_FORGE_MODS.contains(modId) && i18n.translateKey("modmenu.searchTerms.patchwork").toLowerCase(Locale.ROOT).contains(query)) // Search for forge and patchwork + || (ModMenu.hasConfigScreenFactory(modId) && i18n.translateKey("modmenu.searchTerms.configurable").toLowerCase(Locale.ROOT).contains(query)) // Search for mods that can be configured ) { return true; } @@ -57,11 +69,10 @@ private static boolean passesFilters(ModListScreen screen, ModContainer containe private static boolean authorMatches(ModContainer modContainer, String query) { return modContainer.getMetadata().getAuthors().stream() - .filter(Objects::nonNull) - .map(Person::getName) - .filter(Objects::nonNull) - .map(s -> s.toLowerCase(Locale.ROOT)) - .anyMatch(s -> s.contains(query.toLowerCase(Locale.ROOT))); + .filter(Objects::nonNull) + .map(Person::getName) + .filter(Objects::nonNull) + .map(s -> s.toLowerCase(Locale.ROOT)) + .anyMatch(s -> s.contains(query.toLowerCase(Locale.ROOT))); } - } diff --git a/src/main/java/io/github/prospector/modmenu/util/TestModContainer.java b/src/main/java/io/github/prospector/modmenu/util/TestModContainer.java index 8657b19a8..a67f03748 100644 --- a/src/main/java/io/github/prospector/modmenu/util/TestModContainer.java +++ b/src/main/java/io/github/prospector/modmenu/util/TestModContainer.java @@ -214,7 +214,6 @@ public Map getCustomValues() { return new HashMap<>(); } - @SuppressWarnings("UnstableApiUsage") @Override public boolean containsCustomElement(String key) { return false; diff --git a/src/main/resources/assets/modmenu/lang/en_US/en_US.lang b/src/main/resources/assets/modmenu/lang/en_US/en_US.lang index 899411a92..774002d20 100644 --- a/src/main/resources/assets/modmenu/lang/en_US/en_US.lang +++ b/src/main/resources/assets/modmenu/lang/en_US/en_US.lang @@ -13,9 +13,6 @@ modmenu.authors=Authors: modmenu.contributors=Contributors: modmenu.licenses=Licenses: modmenu.search=Search for mods -modmenu.clientsideOnly=Client -modmenu.library=Library -modmenu.deprecated=Deprecated modmenu.toggleFilterOptions=Toggle Filter Options modmenu.showLibraries=Libraries: %s modmenu.showLibraries.true=Shown @@ -23,6 +20,51 @@ modmenu.showLibraries.false=Hidden modmenu.showingMods=Showing %s Mods modmenu.sorting=Sort: %s modmenu.sorting.ascending=A-Z -modmenu.sorting.decending=Z-A -modmenu.minecraft=Minecraft -modmenu.forge=Forge +modmenu.sorting.descending=Z-A + +modmenu.searchTerms.library=api library +modmenu.searchTerms.clientside=clientside gameside +modmenu.searchTerms.deprecated=deprecated outdated old +modmenu.searchTerms.patchwork=patchwork forge fml +modmenu.searchTerms.configurable=configurations configs configures configurable options settings + +modmenu.badge.clientsideOnly=Client +modmenu.badge.library=Library +modmenu.badge.deprecated=Deprecated +modmenu.badge.minecraft=Minecraft +modmenu.badge.forge=Forge + +modmenu.links=Links: +modmenu.sources=Source +modmenu.bluesky=Bluesky +modmenu.buymeacoffee=Buy Me a Coffee +modmenu.coindrop=Coindrop +modmenu.crowdin=Crowdin +modmenu.curseforge=CurseForge +modmenu.discord=Discord +modmenu.donate=Donate +modmenu.flattr=Flattr +modmenu.github_releases=GitHub Releases +modmenu.github_sponsors=GitHub Sponsors +modmenu.kofi=Ko-fi +modmenu.liberapay=Liberapay +modmenu.mastodon=Mastodon +modmenu.modrinth=Modrinth +modmenu.opencollective=Open Collective +modmenu.patreon=Patreon +modmenu.paypal=PayPal +modmenu.reddit=Reddit +modmenu.twitch=Twitch +modmenu.wiki=Wiki +modmenu.twitter=X (Twitter) +modmenu.youtube=YouTube + +modmenu.nameTranslation.minecraft=Better than Adventure! +modmenu.descriptionTranslation.minecraft=The base game. +modmenu.nameTranslation.fabricloader=Fabric Loader +modmenu.descriptionTranslation.fabricloader=The base mod loader. +modmenu.nameTranslation.modmenu=Mod Menu +modmenu.descriptionTranslation.modmenu=Adds a mod menu to view the list of mods you have installed. +modmenu.nameTranslation.java=Java +modmenu.descriptionTranslation.java=The Java runtime environment. +modmenu.javaDistributionName=Running: %s diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index db1082c74..0175450ee 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -9,9 +9,6 @@ "entrypoints": { "main": [ "io.github.prospector.modmenu.ModMenu" - ], - "modmenu": [ - "io.github.prospector.modmenu.impl.ModMenuApiImpl" ] }, "contact": {