diff --git a/TODO.TXT b/TODO.TXT new file mode 100644 index 0000000..b100def --- /dev/null +++ b/TODO.TXT @@ -0,0 +1 @@ +Color should go back to transparent when unselected without needing to click elsewhere (check color updater) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f6a084..8d4cf39 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -minecraft = "1.21.8" -quilt_mappings = "1.21.8+build.1" -fabric_loader = "0.16.14" +minecraft = "1.21.10" +quilt_mappings = "1.21.10+build.4" +fabric_loader = "0.17.3" -fabric_api = "0.130.0+1.21.8" +fabric_api = "0.136.0+1.21.10" # Other mods recursive_resources = "2.7.2+1.21.7" @@ -18,6 +18,6 @@ fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fab recursive_resources = { module = "nl.enjarai:recursive-resources", version.ref = "recursive_resources" } [plugins] -fabric_loom = { id = "fabric-loom", version = "1.10-SNAPSHOT" } -minotaur = { id = "com.modrinth.minotaur", version = "2.8.7" } +fabric_loom = { id = "fabric-loom", version = "1.13.6" } +minotaur = { id = "com.modrinth.minotaur", version = "2.8.10" } cursegradle = { id = "com.matthewprenger.cursegradle", version = "1.4.0" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a79..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/me/bymartrixx/vtd/gui/UnsavedPackWarningScreen.java b/src/main/java/me/bymartrixx/vtd/gui/UnsavedPackWarningScreen.java index b729f1a..8a3ae21 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/UnsavedPackWarningScreen.java +++ b/src/main/java/me/bymartrixx/vtd/gui/UnsavedPackWarningScreen.java @@ -1,39 +1,70 @@ package me.bymartrixx.vtd.gui; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.WarningScreen; import net.minecraft.client.gui.widget.button.ButtonWidget; -import net.minecraft.client.gui.widget.layout.LayoutWidget; -import net.minecraft.client.gui.widget.layout.LinearLayoutWidget; import net.minecraft.text.CommonTexts; +import net.minecraft.text.OrderedText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import me.bymartrixx.vtd.util.Util; -public class UnsavedPackWarningScreen extends WarningScreen { +import java.util.List; + +public class UnsavedPackWarningScreen extends Screen { private static final Text HEADER = Text.translatable("vtd.unsavedPackWarning.header").formatted(Formatting.BOLD); private static final Text MESSAGE = Text.translatable("vtd.unsavedPackWarning.text"); - private static final Text CONFIRM = HEADER.copy().append("\n").append(MESSAGE); private final VTDownloadScreen parent; private final Screen next; + private List messageLines; protected UnsavedPackWarningScreen(VTDownloadScreen parent, Screen next) { - super(HEADER, MESSAGE, CONFIRM); + super(HEADER); this.parent = parent; this.next = next; } @Override - protected LayoutWidget initContent() { - LinearLayoutWidget layout = LinearLayoutWidget.createHorizontal().setSpacing(8); - layout.add(ButtonWidget.builder(CommonTexts.PROCEED, button -> this.client.setScreen(this.next)).build()); - layout.add(ButtonWidget.builder(CommonTexts.BACK, button -> this.closeScreen()).build()); + protected void init() { + this.messageLines = Util.getMultilineTextLines(this.textRenderer, MESSAGE, 10, 300); + + int buttonWidth = 150; + int buttonHeight = 20; + int spacing = 8; + int totalWidth = buttonWidth * 2 + spacing; + int startX = (this.width - totalWidth) / 2; + int buttonY = this.height / 2 + 40; - return layout; + this.addDrawableSelectableElement( + ButtonWidget.builder(CommonTexts.PROCEED, button -> this.client.setScreen(this.next)) + .position(startX, buttonY) + .size(buttonWidth, buttonHeight) + .build()); + + this.addDrawableSelectableElement(ButtonWidget.builder(CommonTexts.BACK, button -> this.closeScreen()) + .position(startX + buttonWidth + spacing, buttonY) + .size(buttonWidth, buttonHeight) + .build()); } @Override public void closeScreen() { this.client.setScreen(this.parent); } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + super.render(graphics, mouseX, mouseY, delta); + + graphics.drawCenteredShadowedText(this.textRenderer, HEADER, this.width / 2, this.height / 2 - 50, 0xFFFFFFFF); + + if (this.messageLines != null) { + int y = this.height / 2 - 20; + for (OrderedText line : this.messageLines) { + graphics.drawCenteredShadowedText(this.textRenderer, line, this.width / 2, y, 0xFFFFFFFF); + y += this.textRenderer.fontHeight + 2; + } + } + } } diff --git a/src/main/java/me/bymartrixx/vtd/gui/VTDownloadScreen.java b/src/main/java/me/bymartrixx/vtd/gui/VTDownloadScreen.java index b0f0795..1b4976e 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/VTDownloadScreen.java +++ b/src/main/java/me/bymartrixx/vtd/gui/VTDownloadScreen.java @@ -221,9 +221,12 @@ private void download() { } private void share() { + if (!this.selectionHelper.hasSelection()) { + return; + } SharePackRequestData data = new SharePackRequestData("resourcepacks", VTDMod.VT_VERSION, this.selectionHelper.getSelectedPacksPrimitive()); - if (data.equals(this.lastShareData)) { + if (data.equals(this.lastShareData) && this.lastShareCode != null) { this.showSharePopup(this.lastShareCode); return; } @@ -233,25 +236,29 @@ private void share() { return; } - VTDMod.executeShare(data).whenCompleteAsync((code, throwable) -> { - if (throwable != null) { - VTDMod.LOGGER.error("Failed to get resource pack share code", throwable); - this.errorPopup.show(ERROR_MESSAGE_TIME, SHARE_FAILED_TEXT.copy() - .append("\n").append(throwable.getLocalizedMessage())); - return; - } + VTDMod.executeShare(data).whenComplete((code, throwable) -> { + // Execute on render thread since showSharePopup needs to access render system + this.client.execute(() -> { + if (throwable != null) { + VTDMod.LOGGER.error("Failed to get resource pack share code", throwable); + this.errorPopup.show(ERROR_MESSAGE_TIME, SHARE_FAILED_TEXT.copy() + .append("\n").append(throwable.getLocalizedMessage())); + return; + } - this.lastShareData = data; - this.lastShareCode = code; + this.lastShareData = data; + this.lastShareCode = code; - this.showSharePopup(code); + this.showSharePopup(code); + }); }); } private void showSharePopup(String code) { if (code != null && this.sharePopup != null) { String url = VTDMod.BASE_URL + "/share#" + code; - this.sharePopup.show(SHARE_MESSAGE_TIME, SHARE_CODE_TEXT.apply(Util.urlText(url))); + Text message = SHARE_CODE_TEXT.apply(Util.urlText(url)); + this.sharePopup.show(SHARE_MESSAGE_TIME, message); } } @@ -387,9 +394,10 @@ protected void init() { // Render over everything else this.progressBar = this.addDrawable(new ProgressBarScreenPopup(this.client, this.width / 2, this.height / 2, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT, PROGRESS_BAR_COLOR)); - this.addDrawable(this.sharePopup); - this.addDrawable(this.errorPopup); - if (this.debugPopup != null) this.addDrawable(this.debugPopup); + // Don't add popups as drawables - we render them explicitly in render() method + // this.addDrawable(this.sharePopup); + // this.addDrawable(this.errorPopup); + // if (this.debugPopup != null) this.addDrawable(this.debugPopup); this.updateButtons(); this.readResourcePack(); @@ -397,7 +405,8 @@ protected void init() { private void updateButtons() { if (this.shareButton != null) { - this.shareButton.active = this.selectionHelper.hasSelection(); + boolean hasSelection = this.selectionHelper.hasSelection(); + this.shareButton.active = hasSelection; } if (this.downloadButton != null) { this.downloadButton.active = this.selectionHelper.hasSelection() && this.packNameField.canUseName(); @@ -411,8 +420,13 @@ private void toggleSelectedPacksListExtended() { this.categorySelector.updateScreenWidth(); this.packSelector.updateScreenWidth(); - this.shareButton.visible = extended; - this.shareButton.setX(this.leftWidth + SELECTED_PACKS_CENTER_X - SHARE_BUTTON_CENTER_X); + if (this.shareButton != null) { + this.shareButton.visible = extended; + this.shareButton.setX(this.leftWidth + SELECTED_PACKS_CENTER_X - SHARE_BUTTON_CENTER_X); + if (extended) { + this.updateButtons(); + } + } } public boolean isCoveredByPopup(int mouseX, int mouseY) { @@ -431,6 +445,16 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { super.render(graphics, mouseX, mouseY, delta); graphics.drawCenteredShadowedText(this.textRenderer, this.title, this.width / 2, TITLE_Y, 0xFFFFFFFF); graphics.drawCenteredShadowedText(this.textRenderer, this.subtitle, this.width / 2, SUBTITLE_Y, 0xFFFFFFFF); + // Render popups explicitly after everything else to ensure they're on top + if (this.sharePopup != null && this.sharePopup.shouldShow()) { + this.sharePopup.render(graphics, mouseX, mouseY, delta); + } + if (this.errorPopup != null && this.errorPopup.shouldShow()) { + this.errorPopup.render(graphics, mouseX, mouseY, delta); + } + if (this.debugPopup != null && this.debugPopup.shouldShow()) { + this.debugPopup.render(graphics, mouseX, mouseY, delta); + } this.renderDebugInfo(graphics, mouseX, mouseY); this.packSelector.renderTooltips(graphics, mouseX, mouseY); diff --git a/src/main/java/me/bymartrixx/vtd/gui/popup/AbstractScreenPopup.java b/src/main/java/me/bymartrixx/vtd/gui/popup/AbstractScreenPopup.java index ff19f7e..e1273b1 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/popup/AbstractScreenPopup.java +++ b/src/main/java/me/bymartrixx/vtd/gui/popup/AbstractScreenPopup.java @@ -32,6 +32,7 @@ public AbstractScreenPopup(MinecraftClient client, int centerX, int centerY, int protected void show(float time) { this.show = true; this.shownTime = time; + this.fadeTime = 0.0F; } public boolean shouldShow() { diff --git a/src/main/java/me/bymartrixx/vtd/gui/popup/MessageScreenPopup.java b/src/main/java/me/bymartrixx/vtd/gui/popup/MessageScreenPopup.java index 21125f9..8732975 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/popup/MessageScreenPopup.java +++ b/src/main/java/me/bymartrixx/vtd/gui/popup/MessageScreenPopup.java @@ -11,6 +11,7 @@ import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.screen.narration.NarrationPart; +import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.text.ClickEvent; import net.minecraft.text.OrderedText; import net.minecraft.text.Style; @@ -54,7 +55,8 @@ public void show(float time, Text message) { this.messageLines = Util.getMultilineTextLines(this.client.textRenderer, message, maxLines, this.maxWidth); this.message = Util.createMultilineText(this.client.textRenderer, message, maxLines, this.maxWidth); - this.updateSize(this.maxWidth, this.getHeight(this.message.count())); + int height = this.getHeight(this.message.count()); + this.updateSize(this.maxWidth, height); this.show(time); } @@ -64,13 +66,19 @@ protected void renderContent(GuiGraphics graphics, int mouseX, int mouseY, float int color = 0xFFFFFF | this.getFadeAlpha() << 24; graphics.drawCenteredShadowedText(textRenderer, this.title, this.centerX, this.getTop() + TITLE_MARGIN, color); - this.message.drawCenteredWithShadow(graphics, - this.centerX, this.getTop() + TITLE_MARGIN * 2 + textRenderer.fontHeight, textRenderer.fontHeight, color); + int y = this.getTop() + TITLE_MARGIN * 2 + textRenderer.fontHeight; + for (OrderedText line : this.messageLines) { + int lineWidth = textRenderer.getWidth(line); + graphics.drawShadowedText(textRenderer, line, this.centerX - lineWidth / 2, y, color); + y += textRenderer.fontHeight; + } } @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (this.shouldShow() && button == GLFW.GLFW_MOUSE_BUTTON_1 + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + double mouseX = this.client.mouse.getX() * this.client.getWindow().getScaledWidth() / this.client.getWindow().getWidth(); + double mouseY = this.client.mouse.getY() * this.client.getWindow().getScaledHeight() / this.client.getWindow().getHeight(); + if (this.shouldShow() && mouseX >= this.getLeft() && mouseX < this.getRight() && mouseY >= this.getTop() && mouseY < this.getBottom()) { double clickedY = mouseY - this.getTop(); @@ -105,7 +113,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } } - return Element.super.mouseClicked(mouseX, mouseY, button); + return false; } // TODO diff --git a/src/main/java/me/bymartrixx/vtd/gui/widget/CategoryButtonWidget.java b/src/main/java/me/bymartrixx/vtd/gui/widget/CategoryButtonWidget.java index 07e4d43..1465d86 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/widget/CategoryButtonWidget.java +++ b/src/main/java/me/bymartrixx/vtd/gui/widget/CategoryButtonWidget.java @@ -11,6 +11,8 @@ import net.minecraft.client.gui.screen.narration.NarrationPart; import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.client.gui.widget.ClickableWidgetStateTextures; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.render.RenderPipelines; import net.minecraft.client.sound.PositionedSoundInstance; import net.minecraft.client.sound.SoundManager; @@ -60,7 +62,7 @@ public void renderButton(GuiGraphics graphics, int x, int y) { } @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { if (this.hovered && !this.selected) { this.playDownSound(MinecraftClient.getInstance().getSoundManager()); return this.screen.selectCategory(this.category); @@ -70,7 +72,8 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + public boolean keyPressed(KeyEvent event) { + int keyCode = event.key(); if (this.selected) { return false; } @@ -80,7 +83,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return this.screen.selectCategory(this.category); } - return Element.super.keyPressed(keyCode, scanCode, modifiers); + return false; } // TODO diff --git a/src/main/java/me/bymartrixx/vtd/gui/widget/CategorySelectionWidget.java b/src/main/java/me/bymartrixx/vtd/gui/widget/CategorySelectionWidget.java index 153f14c..0c2dccc 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/widget/CategorySelectionWidget.java +++ b/src/main/java/me/bymartrixx/vtd/gui/widget/CategorySelectionWidget.java @@ -14,6 +14,7 @@ import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.render.RenderPipelines; import net.minecraft.text.Text; +import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.util.Identifier; import net.minecraft.util.math.MathHelper; import org.joml.Matrix3x2fStack; @@ -187,22 +188,25 @@ private void ensureVisible(CategoryButtonWidget button) { // region input callbacks @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - this.updateScrollingState(mouseX, mouseY, button); + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + MinecraftClient client = MinecraftClient.getInstance(); + double mouseX = client.mouse.getX() * client.getWindow().getScaledWidth() / client.getWindow().getWidth(); + double mouseY = client.mouse.getY() * client.getWindow().getScaledHeight() / client.getWindow().getHeight(); + // Assume left button for scrolling state + this.updateScrollingState(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1); if (!this.isMouseOver(mouseX, mouseY)) { return false; } else { - return super.mouseClicked(mouseX, mouseY, button) || this.scrolling; + return super.mouseClicked(event, bl) || this.scrolling; } } @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - return super.mouseReleased(mouseX, mouseY, button); + public boolean mouseReleased(MouseButtonEvent event) { + return super.mouseReleased(event); } - @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { if (button == GLFW.GLFW_MOUSE_BUTTON_1 && this.scrolling) { // Dragging scrollbar diff --git a/src/main/java/me/bymartrixx/vtd/gui/widget/ExpandDrawerButtonWidget.java b/src/main/java/me/bymartrixx/vtd/gui/widget/ExpandDrawerButtonWidget.java index 3fc69c4..f7c835f 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/widget/ExpandDrawerButtonWidget.java +++ b/src/main/java/me/bymartrixx/vtd/gui/widget/ExpandDrawerButtonWidget.java @@ -5,6 +5,7 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.render.RenderPipelines; import net.minecraft.util.Identifier; import org.lwjgl.glfw.GLFW; @@ -41,14 +42,17 @@ private int getRight() { } @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (this.isMouseOver(mouseX, mouseY) && button == GLFW.GLFW_MOUSE_BUTTON_1) { + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance(); + double mouseX = client.mouse.getX() * client.getWindow().getScaledWidth() / client.getWindow().getWidth(); + double mouseY = client.mouse.getY() * client.getWindow().getScaledHeight() / client.getWindow().getHeight(); + if (this.isMouseOver(mouseX, mouseY)) { this.extended = !this.extended; this.callback.accept(this.extended); return true; } - return Element.super.mouseClicked(mouseX, mouseY, button); + return false; } // TODO diff --git a/src/main/java/me/bymartrixx/vtd/gui/widget/PackSelectionHelper.java b/src/main/java/me/bymartrixx/vtd/gui/widget/PackSelectionHelper.java index dc32c37..2a9f44c 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/widget/PackSelectionHelper.java +++ b/src/main/java/me/bymartrixx/vtd/gui/widget/PackSelectionHelper.java @@ -30,7 +30,8 @@ public class PackSelectionHelper { private final Multimap incompatibilityGroups = LinkedHashMultimap.create(); @VisibleForTesting protected final Map usedColors = new HashMap<>(); - private SelectionChangeCallback selectionChangeCallback = (pack, category, selected) -> {}; + private SelectionChangeCallback selectionChangeCallback = (pack, category, selected) -> { + }; public void buildIncompatibilityGroups(List categories) { this.allIncompatibilityGroups.clear(); @@ -45,7 +46,8 @@ public void buildIncompatibilityGroups(List categories) { for (Pack pack : packs) { int i; - // noinspection SuspiciousMethodCalls DefaultIncompatibilityGroup#equals also works for packs + // noinspection SuspiciousMethodCalls DefaultIncompatibilityGroup#equals also + // works for packs if ((i = this.allIncompatibilityGroups.indexOf(pack)) == -1) { this.allIncompatibilityGroups.add(new DefaultIncompatibilityGroup(pack)); } else { @@ -88,18 +90,18 @@ public void toggleSelection(PackSelectionListWidget.PackEntry entry) { selected = true; } - this.selectionChangeCallback.onSelectionChanged(pack, data.getCategory(), selected); - - if (selected != data.isSelected()) { - data.toggleSelection(); - } - // Remove color for empty incompatibility groups for (IncompatibilityGroup group : this.incompatibilityGroups.get(pack.getId())) { if (this.usedColors.containsKey(group) && !group.hasIncompatibility(this.selection)) { this.usedColors.remove(group); } } + + if (selected != data.isSelected()) { + data.toggleSelection(); + } + + this.selectionChangeCallback.onSelectionChanged(pack, data.getCategory(), selected); } public int getSelectionColor(Pack pack) { diff --git a/src/main/java/me/bymartrixx/vtd/gui/widget/PackSelectionListWidget.java b/src/main/java/me/bymartrixx/vtd/gui/widget/PackSelectionListWidget.java index d2975cb..8f23cf9 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/widget/PackSelectionListWidget.java +++ b/src/main/java/me/bymartrixx/vtd/gui/widget/PackSelectionListWidget.java @@ -20,6 +20,7 @@ import net.minecraft.client.render.RenderPipelines; import net.minecraft.client.sound.PositionedSoundInstance; import net.minecraft.client.sound.SoundManager; +import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.texture.NativeImageBackedTexture; import net.minecraft.client.texture.TextureManager; import net.minecraft.sound.SoundEvents; @@ -50,7 +51,7 @@ public class PackSelectionListWidget extends EntryListWidget getPackEntries(Category category) { if (category.hasWarning()) { //noinspection ConstantConditions - entries.add(new WarningEntry(this.client, this.screen, category.getWarning())); + entries.add(new WarningEntry(this, category.getWarning())); } if (category instanceof Category.SubCategory subCategory) { - entries.add(new ParentCategoryButtonEntry(this.client, this.screen, subCategory)); + entries.add(new ParentCategoryButtonEntry(this, subCategory)); } for (Pack pack : category.getPacks()) { @@ -130,7 +134,7 @@ private List getPackEntries(Category category) { if (category.getSubCategories() != null) { for (Category.SubCategory subCategory : category.getSubCategories()) { - entries.add(new SubCategoryButtonEntry(this.client, this.screen, subCategory)); + entries.add(new SubCategoryButtonEntry(this, subCategory)); } } @@ -153,7 +157,19 @@ public void updateSelection() { private void toggleSelection(PackEntry entry) { if (this.editable) { + boolean wasSelected = entry.selectionData.isSelected(); this.selectionHelper.toggleSelection(entry); + boolean isSelected = entry.selectionData.isSelected(); + + if (wasSelected && !isSelected) { + this.lastUnselectedEntry = entry; + this.lastClickedEntry = null; + } else if (!wasSelected && isSelected) { + this.lastClickedEntry = entry; + this.lastUnselectedEntry = null; + } else { + this.lastClickedEntry = entry; + } } } @@ -209,7 +225,6 @@ protected int method_65507() { return this.getX() + getRowWidth() + ROW_LEFT_RIGHT_MARGIN + SCROLLBAR_LEFT_MARGIN; } - @Override protected boolean isSelectedEntry(int index) { AbstractEntry entry = this.children().get(index); if (entry instanceof PackEntry packEntry) { @@ -256,8 +271,10 @@ public boolean isFocused() { // region input callbacks @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == GLFW.GLFW_MOUSE_BUTTON_1 && this.children().isEmpty()) { + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + double mouseX = this.client.mouse.getX() * this.client.getWindow().getScaledWidth() / this.client.getWindow().getWidth(); + double mouseY = this.client.mouse.getY() * this.client.getWindow().getScaledHeight() / this.client.getWindow().getHeight(); + if (this.children().isEmpty()) { // Handle clicks when the error is shown int x = this.getCenterX(); int textWidth = this.errorText.getMaxWidth(); @@ -295,10 +312,9 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } } - return super.mouseClicked(mouseX, mouseY, button); + return super.mouseClicked(event, bl); } - @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (this.isFocused()) { // if (keyCode == GLFW.GLFW_KEY_DOWN) { @@ -337,20 +353,16 @@ public void drawWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta } } - @Override protected void renderEntry(GuiGraphics graphics, int mouseX, int mouseY, float delta, int index, int entryX, int entryY, int width, int height) { - AbstractEntry entry = this.getEntry(index); + AbstractEntry entry = this.children().get(index); + // Note: Selection highlighting is now handled in method_25343, so we don't draw it here to avoid conflicts with custom colors. Only draw focus outline for non-selected entries. boolean focused = this.isFocused() && this.getFocused() == entry; - if (this.isSelectedEntry(index)) { - int outlineColor = focused ? 0xFFFFFFFF : SELECTION_OUTLINE_COLOR; - int color = this.getEntrySelectionColor(entry); - this.drawEntrySelectionHighlight(graphics, entryY, width, height, outlineColor, color); - } else if (focused) { - RenderUtil.drawOutline(graphics, entryX - 1, entryY - 1, width - 2, height + 2, 1, 0xFFFFFFFF); + if (!this.isSelectedEntry(index) && focused) { + RenderUtil.drawOutline(graphics, entryX - 1, entryY - 1, width - 2, height - 2, 1, 0xFFFFFFFF); } - entry.render(graphics, index, entryY, entryX, width, height, mouseX, mouseY, + entry.renderEntry(graphics, index, entryY, entryX, width, height, mouseX, mouseY, Objects.equals(this.getHoveredEntry(), entry), delta); } @@ -361,7 +373,12 @@ private void renderError(GuiGraphics graphics) { int y = this.getCenterY(); int lineHeight = getLineHeight(textRenderer); - this.errorText.drawCenteredWithShadow(graphics, x, y - lineHeight * 2, lineHeight, 0xFFFFFFFF); + int textY = y - lineHeight * 2; + for (OrderedText line : this.errorLines) { + int lineWidth = this.client.textRenderer.getWidth(line); + graphics.drawShadowedText(this.client.textRenderer, line, x - lineWidth / 2, textY, 0xFFFFFFFF); + textY += lineHeight; + } } public void renderDebugInfo(GuiGraphics graphics, int mouseX, int mouseY) { @@ -421,7 +438,7 @@ public static class PackEntry extends AbstractEntry { protected PackSelectionData selectionData; public PackEntry(PackSelectionListWidget widget, Pack pack) { - super(widget.client, widget.screen); + super(widget); this.pack = pack; this.name = Text.of(pack.getName()).copy().formatted(Formatting.BOLD); this.widget = widget; @@ -433,7 +450,7 @@ public PackEntry(PackSelectionListWidget widget, Pack pack) { this.selectionData = new PackSelectionData(this.pack, widget.category); } - private List getDescriptionLines(int maxWidth) { + protected List getDescriptionLines(int maxWidth) { return this.wrapEscapedText(this.pack.getDescription(), maxWidth); } @@ -495,29 +512,112 @@ protected List getTooltipText(int width) { } @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == GLFW.GLFW_MOUSE_BUTTON_1) { - this.widget.toggleSelection(this); - return true; - } - - return false; + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + this.widget.toggleSelection(this); + return true; } // region entryRender @Override - public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + public void renderEntry(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { TextRenderer textRenderer = this.client.textRenderer; - int iconSize = entryHeight - ICON_MARGIN * 2; - int centerX = x + (iconSize + entryWidth) / 2; // center over area left to the icon - graphics.drawCenteredShadowedText(textRenderer, this.name, centerX, y, 0xFFFFFFFF); - - this.renderDescription(graphics, centerX, y + getLineHeight(textRenderer), entryWidth - iconSize); - if (!DISABLE_ICONS) this.renderIcon(graphics, x + ICON_MARGIN, y + ICON_MARGIN, iconSize); + // Keep icon size fixed based on ITEM_HEIGHT, not entryHeight (reduced to 75%) + int iconSize = (int) ((ITEM_HEIGHT - ICON_MARGIN * 2) * 0.75); + + // Calculate equal spacing: top, left, bottom between icon and border + // Border is at x, so we want equal spacing on all sides + int spacing = (entryHeight - iconSize) / 2; // Equal top and bottom spacing + int iconX = x + spacing; // Left spacing equals top/bottom spacing + int iconY = y + spacing; // Top spacing + + // Text area starts after icon with spacing + int textAreaX = iconX + iconSize + spacing; + int textAreaWidth = entryWidth - (textAreaX - x); + + // Calculate vertical centering (title + max 2 description lines) + int lineHeight = getLineHeight(textRenderer); + int totalTextHeight = lineHeight * 2; // title + description (max 2 lines) + int textStartY = y + (entryHeight - totalTextHeight) / 2; + int centerX = textAreaX + textAreaWidth / 2; // center in text area + + graphics.drawCenteredShadowedText(textRenderer, this.name, centerX, textStartY, 0xFFFFFFFF); + + // Use textAreaWidth for description to ensure proper wrapping when menu is open + this.renderDescription(graphics, centerX, textStartY + lineHeight, textAreaWidth); + + // Render icon with equal spacing + if (!DISABLE_ICONS) { + this.renderIcon(graphics, iconX, iconY, iconSize); + } } private void renderDescription(GuiGraphics graphics, int x, int y, int width) { - getShortDescription(width - TEXT_MARGIN).drawCenteredWithShadow(graphics, x, y); + List descLines = this.getDescriptionLines(width - TEXT_MARGIN); + TextRenderer textRenderer = this.client.textRenderer; + int maxWidth = width - TEXT_MARGIN; + + // Limit to maximum 2 lines, truncating at last "." or "," if needed + int descY = y; + if (descLines.size() == 0) { + return; + } + + // first line + Text firstLine = descLines.get(0); + int firstLineWidth = textRenderer.getWidth(firstLine); + graphics.drawShadowedText(textRenderer, firstLine, x - firstLineWidth / 2, descY, 0xFFFFFFFF); + descY += textRenderer.fontHeight; + + // second line with truncation + if (descLines.size() >= 2) { + Text secondLine = descLines.get(1); + int secondLineWidth = textRenderer.getWidth(secondLine); + + // If there are more than 2 lines, or if the second line doesn't fit, truncate at last punctuation + if (descLines.size() > 2 || secondLineWidth > maxWidth) { + Text truncatedLine = truncateAtLastPunctuation(secondLine, maxWidth, textRenderer); + int truncatedWidth = textRenderer.getWidth(truncatedLine); + graphics.drawShadowedText(textRenderer, truncatedLine, x - truncatedWidth / 2, descY, 0xFFFFFFFF); + } else { + graphics.drawShadowedText(textRenderer, secondLine, x - secondLineWidth / 2, descY, 0xFFFFFFFF); + } + } + } + + private Text truncateAtLastPunctuation(Text originalText, int maxWidth, TextRenderer textRenderer) { + String text = originalText.getString(); + int lastPunctIndex = -1; + + // Find the last "." or "," that fits within maxWidth + for (int i = text.length() - 1; i >= 0; i--) { + char c = text.charAt(i); + if (c == '.' || c == ',') { + String candidate = text.substring(0, i + 1); + Text candidateText = Text.of(candidate).copy().setStyle(originalText.getStyle()); + if (textRenderer.getWidth(candidateText) <= maxWidth) { + lastPunctIndex = i + 1; + break; + } + } + } + + // If we found a punctuation mark, truncate there + if (lastPunctIndex > 0) { + String truncated = text.substring(0, lastPunctIndex); + return Text.of(truncated).copy().setStyle(originalText.getStyle()); + } + + // If no punctuation found, truncate to fit maxWidth + for (int i = text.length(); i > 0; i--) { + String candidate = text.substring(0, i); + Text candidateText = Text.of(candidate).copy().setStyle(originalText.getStyle()); + if (textRenderer.getWidth(candidateText) <= maxWidth) { + return candidateText; + } + } + + // Fallback: return empty or first character + return Text.empty(); } private void renderIcon(GuiGraphics graphics, int x, int y, int size) { @@ -541,8 +641,8 @@ public static class WarningEntry extends AbstractEntry { private List textLines; private MultilineText text; - public WarningEntry(MinecraftClient client, VTDownloadScreen screen, Category.Warning warning) { - super(client, screen); + public WarningEntry(PackSelectionListWidget widget, Category.Warning warning) { + super(widget); this.warning = warning; this.color = Util.parseColor(warning.getColor()); @@ -577,7 +677,7 @@ protected List getTooltipText(int width) { // region warningRender @Override - public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + public void renderEntry(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { this.renderBackground(graphics, x + WARNING_BG_MARGIN, y + WARNING_BG_MARGIN, entryWidth - WARNING_BG_MARGIN * 2, entryHeight - WARNING_BG_MARGIN * 2); int width = entryWidth - WARNING_MARGIN * 2; @@ -589,7 +689,13 @@ private void renderBackground(GuiGraphics graphics, int x, int y, int width, int } private void renderText(GuiGraphics graphics, int x, int y, int width) { - this.getText(width).drawCenteredWithShadow(graphics, x, y); + List lines = this.getTextLines(width); + int textY = y; + for (Text line : lines) { + int lineWidth = this.client.textRenderer.getWidth(line); + graphics.drawShadowedText(this.client.textRenderer, line, x - lineWidth / 2, textY, 0xFFFFFFFF); + textY += this.client.textRenderer.fontHeight; + } } // endregion @@ -600,8 +706,8 @@ public String toString() { } public static class SubCategoryButtonEntry extends CategoryButtonEntry { - protected SubCategoryButtonEntry(MinecraftClient client, VTDownloadScreen screen, Category.SubCategory category) { - super(client, screen, category); + protected SubCategoryButtonEntry(PackSelectionListWidget widget, Category.SubCategory category) { + super(widget, category); } @Override @@ -611,8 +717,8 @@ public String toString() { } public static class ParentCategoryButtonEntry extends CategoryButtonEntry { - protected ParentCategoryButtonEntry(MinecraftClient client, VTDownloadScreen screen, Category.SubCategory subCategory) { - super(client, screen, subCategory.getParent()); + protected ParentCategoryButtonEntry(PackSelectionListWidget widget, Category.SubCategory subCategory) { + super(widget, subCategory.getParent()); } @Override @@ -629,8 +735,8 @@ public abstract static class CategoryButtonEntry extends AbstractEntry { protected final Category category; protected final Text name; - protected CategoryButtonEntry(MinecraftClient client, VTDownloadScreen screen, Category category) { - super(client, screen); + protected CategoryButtonEntry(PackSelectionListWidget widget, Category category) { + super(widget); this.category = category; this.name = Text.literal(category.getName()).formatted(Formatting.BOLD); } @@ -645,18 +751,14 @@ private void playDownSound(SoundManager soundManager) { } @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == GLFW.GLFW_MOUSE_BUTTON_1) { - this.screen.selectCategory(this.category); - this.playDownSound(this.client.getSoundManager()); - return true; - } - - return false; + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + this.screen.selectCategory(this.category); + this.playDownSound(this.client.getSoundManager()); + return true; } @Override - public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + public void renderEntry(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { graphics.drawSprite(RenderPipelines.GUI_TEXTURED, TEXTURE, x + BUTTON_HORIZONTAL_PADDING, y + (entryHeight - BUTTON_HEIGHT) / 2, entryWidth - BUTTON_HORIZONTAL_PADDING * 2, BUTTON_HEIGHT); graphics.drawCenteredShadowedText(this.client.textRenderer, this.name, x + entryWidth / 2, y + (entryHeight - this.client.textRenderer.fontHeight) / 2, 0xFFFFFFFF); @@ -666,10 +768,12 @@ public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth public static abstract class AbstractEntry extends EntryListWidget.Entry { protected final MinecraftClient client; protected final VTDownloadScreen screen; + protected final PackSelectionListWidget parentWidget; - protected AbstractEntry(MinecraftClient client, VTDownloadScreen screen) { - this.client = client; - this.screen = screen; + protected AbstractEntry(PackSelectionListWidget widget) { + this.client = widget.client; + this.screen = widget.screen; + this.parentWidget = widget; } protected final List wrapEscapedText(String text, int maxWidth) { @@ -682,6 +786,63 @@ protected final MultilineText createMultilineText(List lines) { protected abstract List getTooltipText(int width); + public abstract void renderEntry(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta); + + // Required method for Minecraft 1.21.10+ API method_25343 receives mouse coordinates, not entry position + @Override + public void method_25343(GuiGraphics graphics, int mouseX, int mouseY, boolean hovered, float tickDelta) { + // Calculate entry position from widget + int entryIndex = this.parentWidget.children().indexOf(this); + if (entryIndex < 0) return; + + int x = this.parentWidget.getRowLeft(); + int y = this.parentWidget.getRowTop(entryIndex); + int entryWidth = this.parentWidget.getRowWidth(); + int entryHeight = ITEM_HEIGHT; + + // Draw background and outline for selected entries + // Draw background based on selection state for PackEntry + if (this instanceof PackEntry) { + int backgroundWidth = entryWidth; + int color; + + if (this.parentWidget.isSelectedEntry(entryIndex)) { + // Selected: Green background + color = 0xFF006400; // Dark Green + } else { + // Unselected: Dark Grey background + color = 0xFF222222; // Dark Grey + } + + // Draw the background + graphics.fill(x, y, x + backgroundWidth, y + entryHeight, color); + + // Draw outline if selected or focused + if (this.parentWidget.isSelectedEntry(entryIndex)) { + boolean isLastClicked = this.parentWidget.lastClickedEntry == this; + int outlineColor = isLastClicked ? 0xFFFFFFFF : 0xFF808080; + RenderUtil.drawEntrySelectionHighlight(graphics, x, y, backgroundWidth, entryHeight, outlineColor, + color); + } + } else if (this.parentWidget.isSelectedEntry(entryIndex)) { + int backgroundWidth = entryWidth; + boolean isLastClicked = this instanceof PackEntry && this.parentWidget.lastClickedEntry == this; + + if (isLastClicked) { + // White outline for last clicked entry + int outlineColor = 0xFFFFFFFF; // White outline + int color = 0xE0000000; // Black fill + RenderUtil.drawEntrySelectionHighlight(graphics, x, y, backgroundWidth, entryHeight, outlineColor, color); + } else { + // Grey outline for selected entries (not last clicked) + int outlineColor = 0xFF808080; // Grey outline + int color = 0xE0000000; // Black fill + RenderUtil.drawEntrySelectionHighlight(graphics, x, y, backgroundWidth, entryHeight, outlineColor, color); + } + } + this.renderEntry(graphics, entryIndex, y, x, entryWidth, entryHeight, mouseX, mouseY, hovered, tickDelta); + } + // region baseEntryRender protected boolean renderTooltip(GuiGraphics graphics, int mouseX, int mouseY, int width) { if (this.isMouseOver(mouseX, mouseY)) { diff --git a/src/main/java/me/bymartrixx/vtd/gui/widget/SelectedPacksListWidget.java b/src/main/java/me/bymartrixx/vtd/gui/widget/SelectedPacksListWidget.java index a1053bb..94b0168 100644 --- a/src/main/java/me/bymartrixx/vtd/gui/widget/SelectedPacksListWidget.java +++ b/src/main/java/me/bymartrixx/vtd/gui/widget/SelectedPacksListWidget.java @@ -9,6 +9,7 @@ import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.screen.narration.NarrationPart; import net.minecraft.client.gui.widget.list.EntryListWidget; +import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Util; @@ -26,6 +27,7 @@ public class SelectedPacksListWidget extends EntryListWidget entries = new java.util.ArrayList<>(this.children()); + entries.add(index, entry); + this.replaceEntries(entries); + } private int getPackEntryIndex(Pack pack) { for (int i = 0; i < this.children().size(); i++) { @@ -208,14 +221,13 @@ private int getLastChildIndex(Category category) { return index; } - @Override protected boolean isSelectedEntry(int index) { - return this.getFocused() == this.getEntry(index); + return this.getFocused() == this.children().get(index); } @Override public int getRowWidth() { - return this.width - ROW_LEFT_RIGHT_MARGIN * 2 - field_55258 - SCROLLBAR_LEFT_MARGIN; + return this.width - ROW_LEFT_RIGHT_MARGIN * 2 - 6 - SCROLLBAR_LEFT_MARGIN; } @Override @@ -253,8 +265,35 @@ public boolean isFocused() { public boolean isMouseOver(double mouseX, double mouseY) { return this.extended && super.isMouseOver(mouseX, mouseY); } - + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + if (!this.extended) { + return false; + } + double mouseX = this.client.mouse.getX() * this.client.getWindow().getScaledWidth() / this.client.getWindow().getWidth(); + double mouseY = this.client.mouse.getY() * this.client.getWindow().getScaledHeight() / this.client.getWindow().getHeight(); + + if (!this.isMouseOver(mouseX, mouseY)) { + return false; + } + int rowLeft = this.getRowLeft(); + int rowRight = this.getRowRight(); + + if (mouseX >= rowLeft && mouseX < rowRight) { + for (int i = 0; i < this.children().size(); i++) { + int entryTop = this.getRowTop(i); + int entryBottom = entryTop + ITEM_HEIGHT; + + if (mouseY >= entryTop && mouseY < entryBottom) { + AbstractEntry entry = this.children().get(i); + return entry.mouseClicked(event, bl); + } + } + } + return super.mouseClicked(event, bl); + } + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (this.isFocused()) { // if (keyCode == GLFW.GLFW_KEY_DOWN) { @@ -293,10 +332,16 @@ public void drawWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta return; } + int headerY = this.getY(); + this.renderHeader(graphics, this.getRowLeft(), headerY); super.drawWidget(graphics, mouseX, mouseY, delta); } - + @Override + public int getRowTop(int index) { + return super.getRowTop(index) + 20; + } + protected void renderHeader(GuiGraphics graphics, int x, int y) { graphics.drawCenteredShadowedText(this.client.textRenderer, HEADER, this.getRowLeft() + this.getRowWidth() / 2, y, 0xFFFFFFFF); } @@ -356,8 +401,7 @@ protected void drawScrollingText(GuiGraphics graphics, int x, int y, int maxWidt } } - @Override - public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + public void renderEntry(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { int color = this.getColor(); graphics.drawShadowedString(this.client.textRenderer, this.textPrefix, x, y, color); int offsetX = this.client.textRenderer.getWidth(this.textPrefix); @@ -367,6 +411,22 @@ public void render(GuiGraphics graphics, int index, int y, int x, int entryWidth this.drawScrollingText(graphics, x, y, entryWidth, entryHeight, color); } } + + // Required method for Minecraft 1.21.10+ API method_25343 receives mouse coordinates, not entry position + @Override + public void method_25343(GuiGraphics graphics, int mouseX, int mouseY, boolean hovered, float tickDelta) { + // Calculate entry position from widget + int entryIndex = this.widget.children().indexOf(this); + if (entryIndex < 0) return; + + int x = this.widget.getRowLeft(); + int y = this.widget.getRowTop(entryIndex); + int entryWidth = this.widget.getRowWidth(); + int entryHeight = ITEM_HEIGHT; + + // Call renderEntry with the calculated position + this.renderEntry(graphics, entryIndex, y, x, entryWidth, entryHeight, mouseX, mouseY, hovered, tickDelta); + } } public static class CategoryEntry extends AbstractEntry { @@ -379,17 +439,13 @@ public CategoryEntry(SelectedPacksListWidget widget, Category category) { } @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == GLFW.GLFW_MOUSE_BUTTON_1) { - long time = System.currentTimeMillis(); - if (time <= this.lastClickTime + DOUBLE_CLICK_THRESHOLD) { - this.selectEntry(); - } - - this.lastClickTime = time; + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + long time = System.currentTimeMillis(); + if (time <= this.lastClickTime + DOUBLE_CLICK_THRESHOLD) { + this.selectEntry(); } - - return super.mouseClicked(mouseX, mouseY, button); + this.lastClickTime = time; + return false; } @Override @@ -483,17 +539,13 @@ protected void selectEntry() { } @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == GLFW.GLFW_MOUSE_BUTTON_1) { - long time = System.currentTimeMillis(); - if (time <= this.lastClickTime + DOUBLE_CLICK_THRESHOLD) { - this.selectEntry(); - } - - this.lastClickTime = time; + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + long time = System.currentTimeMillis(); + if (time <= this.lastClickTime + DOUBLE_CLICK_THRESHOLD) { + this.selectEntry(); } - - return super.mouseClicked(mouseX, mouseY, button); + this.lastClickTime = time; + return false; } @Override diff --git a/src/main/java/me/bymartrixx/vtd/mixin/PackEntryListWidgetMixin.java b/src/main/java/me/bymartrixx/vtd/mixin/PackEntryListWidgetMixin.java index 314b5cf..b338cff 100644 --- a/src/main/java/me/bymartrixx/vtd/mixin/PackEntryListWidgetMixin.java +++ b/src/main/java/me/bymartrixx/vtd/mixin/PackEntryListWidgetMixin.java @@ -21,10 +21,10 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import net.minecraft.client.input.MouseButtonEvent; @Mixin(PackEntryListWidget.class) -public abstract class PackEntryListWidgetMixin extends AlwaysSelectedEntryListWidget +public abstract class PackEntryListWidgetMixin implements PackEntryListWidgetAccess { @Shadow @Final private Text title; @@ -32,10 +32,6 @@ public abstract class PackEntryListWidgetMixin extends AlwaysSelectedEntryListWi @Shadow @Final PackScreen screen; - private PackEntryListWidgetMixin(MinecraftClient client, int width, int height, int y, int itemHeight) { - super(client, width, height, y, itemHeight); - } - @Override public boolean vtdownloader$isAvailablePackList() { // Available packs list uses "pack.available.title" as title @@ -44,7 +40,7 @@ private PackEntryListWidgetMixin(MinecraftClient client, int width, int height, @Override public int vtdownloader$getItemHeight() { - return this.itemHeight; + return 36; } @Override @@ -79,53 +75,81 @@ public static abstract class PackEntryMixin { private boolean vtdownloader$vtPack; @Unique private boolean vtdownloader$editable; + @Unique + private int vtdownloader$lastRenderX; + @Unique + private int vtdownloader$lastRenderY; + // In 1.21.10+, inner class constructor injection needs the outer class as first parameter @Inject(at = @At("TAIL"), method = "") - private void vtdownloader$init(MinecraftClient client, PackEntryListWidget widget, ResourcePackOrganizer.Pack pack, CallbackInfo ci) { + private void vtdownloader$init(PackEntryListWidget outer, MinecraftClient client, PackEntryListWidget widget, ResourcePackOrganizer.Pack pack, CallbackInfo ci) { if (((PackEntryListWidgetAccess) widget).vtdownloader$isResourcePackList()) { this.vtdownloader$vtPack = pack.getDescription().getString().contains(Constants.VT_DESCRIPTION_MARKER); this.vtdownloader$editable = this.vtdownloader$vtPack && ((PackEntryListWidgetAccess) this.widget).vtdownloader$isAvailablePackList(); } } - @Inject(at = @At("TAIL"), method = "render") - private void renderEditButton(GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, - int mouseX, int mouseY, boolean hovered, float tickDelta, CallbackInfo ci) { + // In Minecraft 1.21.10+, the render method is method_25343 with different signature + @Inject(at = @At("TAIL"), method = "method_25343", remap = false) + private void renderEditButton(GuiGraphics graphics, int mouseX, int mouseY, boolean hovered, float tickDelta, CallbackInfo ci) { if (this.vtdownloader$vtPack) { + // Calculate entry position from widget + int entryIndex = this.widget.children().indexOf(this); + if (entryIndex < 0) return; + + int itemHeight = ((PackEntryListWidgetAccess) this.widget).vtdownloader$getItemHeight(); + int entryWidth = this.widget.getRowWidth(); + + // Get entry position from widget's row calculations + int x = this.widget.getRowLeft(); + int y = this.widget.getRowTop(entryIndex); + int pencilX = x + entryWidth - PENCIL_SIZE - PENCIL_RIGHT_MARGIN; - int pencilY = y + entryHeight - PENCIL_SIZE - PENCIL_BOTTOM_MARGIN; + int pencilY = y + itemHeight - PENCIL_SIZE - PENCIL_BOTTOM_MARGIN; + + // Check if mouse is directly over the pencil icon (not just the entry) + boolean mouseOverPencil = mouseX >= pencilX && mouseX < pencilX + PENCIL_SIZE + && mouseY >= pencilY && mouseY < pencilY + PENCIL_SIZE; + float u = 0.0F; float v = 0.0F; if (!this.vtdownloader$editable) { v = PENCIL_SIZE; - } else if (mouseX >= pencilX && mouseX < pencilX + PENCIL_SIZE - && mouseY >= pencilY && mouseY < pencilY + PENCIL_SIZE) { + } else if (mouseOverPencil) { + // Show white overlay only when cursor is directly over the pencil u = PENCIL_SIZE; } + // Store position for click detection + this.vtdownloader$lastRenderX = x; + this.vtdownloader$lastRenderY = y; + graphics.drawTexture(RenderPipelines.GUI_TEXTURED, Constants.PENCIL_TEXTURE, pencilX, pencilY, u, v, PENCIL_SIZE, PENCIL_SIZE, PENCIL_TEXTURE_SIZE, PENCIL_TEXTURE_SIZE); } } - // @version 1.19.2 - 1.20.6 - @SuppressWarnings("InvalidInjectorMethodSignature") // Plugin gives invalid params - @Inject(at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/gui/widget/list/pack/PackEntryListWidget$PackEntry;isSelectable()Z" - ), method = "mouseClicked", locals = LocalCapture.CAPTURE_FAILHARD) - private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir, - double clickedX, double clickedY) { - if (this.vtdownloader$editable) { - double pencilX = this.widget.getRowWidth() - PENCIL_SIZE - PENCIL_RIGHT_MARGIN; - double pencilY = ((PackEntryListWidgetAccess) this.widget).vtdownloader$getItemHeight() - 4 - PENCIL_SIZE - PENCIL_BOTTOM_MARGIN; - - if (clickedX >= pencilX && clickedX < pencilX + PENCIL_SIZE - && clickedY >= pencilY && clickedY < pencilY + PENCIL_SIZE) { + // Handle click on pencil icon + @Inject(at = @At("HEAD"), method = "mouseClicked", cancellable = true) + private void onMouseClicked(MouseButtonEvent event, boolean hovered, CallbackInfoReturnable cir) { + if (this.vtdownloader$editable && this.vtdownloader$lastRenderX > 0) { + // Get mouse position from client + double mouseX = this.client.mouse.getX() * this.client.getWindow().getScaledWidth() / this.client.getWindow().getWidth(); + double mouseY = this.client.mouse.getY() * this.client.getWindow().getScaledHeight() / this.client.getWindow().getHeight(); + + int itemHeight = ((PackEntryListWidgetAccess) this.widget).vtdownloader$getItemHeight(); + int entryWidth = this.widget.getRowWidth(); + + int pencilX = this.vtdownloader$lastRenderX + entryWidth - PENCIL_SIZE - PENCIL_RIGHT_MARGIN; + int pencilY = this.vtdownloader$lastRenderY + itemHeight - PENCIL_SIZE - PENCIL_BOTTOM_MARGIN; + + if (mouseX >= pencilX && mouseX < pencilX + PENCIL_SIZE + && mouseY >= pencilY && mouseY < pencilY + PENCIL_SIZE) { PackScreen screen = ((PackEntryListWidgetAccess) this.widget).vtdownloader$getScreen(); ((PackScreenAccess) screen).vtdownloader$applyChanges(); this.client.setScreen(new VTDownloadScreen(screen, Constants.RESOURCE_PACK_SCREEN_SUBTITLE, this.pack)); + cir.setReturnValue(true); } } } diff --git a/src/main/java/me/bymartrixx/vtd/util/RenderUtil.java b/src/main/java/me/bymartrixx/vtd/util/RenderUtil.java index 1212789..7955c41 100644 --- a/src/main/java/me/bymartrixx/vtd/util/RenderUtil.java +++ b/src/main/java/me/bymartrixx/vtd/util/RenderUtil.java @@ -16,6 +16,15 @@ public static void drawOutline(GuiGraphics graphics, int x, int y, int width, in graphics.fill(x + width, y, x + width + size, y + height, color); // Right line } + public static void drawEntrySelectionHighlight(GuiGraphics graphics, int x, int y, int width, int height, int outlineColor, int fillColor) { + int outlineSize = 1; + graphics.fill(x, y, x + width, y + height, fillColor); + graphics.fill(x, y, x + width, y + outlineSize, outlineColor); // Top + graphics.fill(x, y + height - outlineSize, x + width, y + height, outlineColor); // Bottom + graphics.fill(x, y, x + outlineSize, y + height, outlineColor); // Left + graphics.fill(x + width - outlineSize, y, x + width, y + height, outlineColor); // Right + } + public static void renderDebugInfo(GuiGraphics graphics, TextRenderer textRenderer, int x, int endY, List info) { // Make text half its size graphics.getMatrices().pushMatrix();