diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxAccessor.java index cec11f3bf29..9298465f2ae 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxAccessor.java +++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxAccessor.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.mixins.accessors; import net.minecraft.client.gui.components.Checkbox; +import net.minecraft.client.gui.components.MultiLineTextWidget; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @@ -8,4 +9,7 @@ public interface CheckboxAccessor { @Accessor void setSelected(boolean checked); + + @Accessor + MultiLineTextWidget getTextWidget(); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java index 80f5800db0f..011c61500a3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.skyblock.chat; +import de.hysky.skyblocker.mixins.accessors.CheckboxAccessor; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.utils.FlexibleItemStack; import de.hysky.skyblocker.utils.Formatters; @@ -10,16 +11,20 @@ import de.hysky.skyblocker.utils.render.gui.ToggleableLayoutWidget; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.ActiveTextCollector; import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.TextAlignment; import net.minecraft.client.gui.components.AbstractContainerWidget; import net.minecraft.client.gui.components.AbstractScrollArea; import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Checkbox; import net.minecraft.client.gui.components.CycleButton; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.MultiLineTextWidget; import net.minecraft.client.gui.components.StringWidget; import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.components.events.ContainerEventHandler; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.layouts.FrameLayout; import net.minecraft.client.gui.layouts.GridLayout; @@ -35,6 +40,7 @@ import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; @@ -45,6 +51,7 @@ import net.minecraft.world.item.ItemStack; import org.jspecify.annotations.Nullable; +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -59,8 +66,10 @@ public class ChatRuleConfigScreen extends Screen { private static final int GRID_SPACING = 2; protected static final Identifier SEARCH_ICON_TEXTURE = Identifier.withDefaultNamespace("icon/search"); private static final FlexibleItemStack INVALID_ITEM = Ico.BARRIER; - private static final Component YES_TEXT = CommonComponents.GUI_YES.copy().withStyle(ChatFormatting.GREEN); - private static final Component NO_TEXT = CommonComponents.GUI_NO.copy().withStyle(ChatFormatting.RED); + // Link to helpful learning & testing website for regex w/ multilingual support. + private static final Component REGEX_LINK = Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regexLink").withStyle( + style -> style.withUnderlined(true).withClickEvent(new ClickEvent.OpenUrl(URI.create("https://regex101.com/"))) + ); private final Map<@Nullable SoundEvent, Component> soundNames = Util.make(new Object2ObjectOpenHashMap<>(), map -> { map.put(SoundEvents.NOTE_BLOCK_PLING.value(), Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.pling").withStyle(ChatFormatting.YELLOW)); @@ -125,24 +134,44 @@ protected void init() { // Filter settings LinearLayout filtersRow1 = contentAdder.addChild(LinearLayout.horizontal().spacing(GRID_SPACING), 3); - filtersRow1.addChild(CycleButton.booleanBuilder(YES_TEXT, NO_TEXT, chatRule.getRegex()) - .withTooltip(_ -> Tooltip.create(Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex.@Tooltip"))) - .create(0, 0, getWidth(1.5f), 20, Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex"), (_, value) -> chatRule.setRegex(value))); + filtersRow1.addChild(buildCheckbox( + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex", REGEX_LINK), + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex.@Tooltip"), + getWidth(1.5f), + TextAlignment.CENTER, + chatRule::setRegex, + chatRule.getRegex() + )); filtersRow1.addChild(Button.builder(Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locations"), _ -> minecraft.setScreen(new ChatRuleLocationConfigScreen(this, chatRule))) .tooltip(Tooltip.create(Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locations.@Tooltip"))) .width(getWidth(1.5f)) .build()); LinearLayout filtersRow2 = contentAdder.addChild(LinearLayout.horizontal().spacing(GRID_SPACING), 3); - filtersRow2.addChild(CycleButton.booleanBuilder(YES_TEXT, NO_TEXT, chatRule.getIncludeFormatting()) - .withTooltip(_ -> Tooltip.create(Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.includeFormatting.@Tooltip"))) - .create(0, 0, getWidth(1f), 20, Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.includeFormatting"), (_, value) -> chatRule.setIncludeFormatting(value))); - filtersRow2.addChild(CycleButton.booleanBuilder(YES_TEXT, NO_TEXT, chatRule.getPartialMatch()) - .withTooltip(_ -> Tooltip.create(Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch.@Tooltip"))) - .create(0, 0, getWidth(1f), 20, Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch"), (_, value) -> chatRule.setPartialMatch(value))); - filtersRow2.addChild(CycleButton.booleanBuilder(YES_TEXT, NO_TEXT, chatRule.getIgnoreCase()) - .withTooltip(_ -> Tooltip.create(Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase.@Tooltip"))) - .create(0, 0, getWidth(1f), 20, Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase"), (_, value) -> chatRule.setIgnoreCase(value))); + filtersRow2.addChild(buildCheckbox( + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.includeFormatting"), + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.includeFormatting.@Tooltip"), + getWidth(1.25f), + TextAlignment.LEFT, + chatRule::setIncludeFormatting, + chatRule.getIncludeFormatting() + )); + filtersRow2.addChild(buildCheckbox( + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch"), + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch.@Tooltip"), + getWidth(0.75f), + TextAlignment.CENTER, + chatRule::setPartialMatch, + chatRule.getPartialMatch() + )); + filtersRow2.addChild(buildCheckbox( + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase"), + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase.@Tooltip"), + getWidth(1f), + TextAlignment.RIGHT, + chatRule::setIgnoreCase, + chatRule.getIgnoreCase() + )); // ==== Outputs contentAdder.addChild(new StringWidget(Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.outputs").withStyle(ChatFormatting.BOLD, ChatFormatting.UNDERLINE), font), 3, content.newCellSettings().paddingTop(4 + GRID_SPACING)); @@ -150,12 +179,17 @@ protected void init() { LinearLayout buttons = contentAdder.addChild(LinearLayout.horizontal().spacing(GRID_SPACING), 3); - buttons.addChild(CycleButton.booleanBuilder(YES_TEXT, NO_TEXT, chatRule.getHideMessage()) - .withTooltip(_ -> Tooltip.create(Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage.@Tooltip"))) - .create(0, 0, getWidth(1.5f), 20, Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage"), (_, value) -> { + buttons.addChild(buildCheckbox( + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage"), + Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage.@Tooltip"), + getWidth(1.5f), + TextAlignment.LEFT, + value -> { chatRule.setHideMessage(value); recreateLayout(); - })); + }, + chatRule.getHideMessage() + )); // Sound // In case the user has a sound not in the list added to the config. We abuse the fact that we can have alternative values. @@ -312,6 +346,24 @@ private static String getItemString(ItemStack stack) { return BuiltInRegistries.ITEM.getKey(stack.getItem()) + ItemStackComponentizationFixer.componentsAsString(stack); } + private FrameLayout buildCheckbox(Component text, Component tooltip, int width, TextAlignment align, Consumer setter, boolean selected) { + FrameLayout frame = new FrameLayout().setMinWidth(width); + Checkbox box = Checkbox.builder(text, font) + .selected(selected) + .onValueChange((_, value) -> setter.accept(value)) + .maxWidth(width) + .build(); + + switch (align) { + case LEFT -> frame.defaultChildLayoutSetting().alignHorizontallyLeft(); + case CENTER -> frame.defaultChildLayoutSetting().alignHorizontallyCenter(); + case RIGHT -> frame.defaultChildLayoutSetting().alignHorizontallyRight(); + } + box.setTooltip(Tooltip.create(tooltip)); + frame.addChild(box); + + return frame; + } private EditBox.TextFormatter createRenderTextProvider(Supplier fullTextSupplier) { return createRenderTextProvider(fullTextSupplier, false); @@ -360,6 +412,30 @@ protected void repositionElements() { layout.arrangeElements(); } + /** + * Handle click events for checkbox messages + */ + @Override + public boolean mouseClicked(final MouseButtonEvent event, final boolean doubleClick) { + Optional child = getChildAt(event.x(), event.y()); + // Enter all containers until non-container is reached. + while (child.isPresent() && child.get() instanceof ContainerEventHandler container) { + child = container.getChildAt(event.x(), event.y()); + } + if (child.isPresent() && child.get() instanceof Checkbox check) { + ActiveTextCollector.ClickableStyleFinder finder = (new ActiveTextCollector.ClickableStyleFinder(font, (int) event.x(), (int) event.y())) + .includeInsertions(false); + ((CheckboxAccessor) check).getTextWidget().visitLines(finder); + final Style style = finder.result(); + // unnecessary null check on click event to suppress warning + if (style != null && style.getClickEvent() != null) { + defaultHandleClickEvent(style.getClickEvent(), minecraft, this); + return true; + } + } + return super.mouseClicked(event, doubleClick); + } + /** * Saves and returns to parent screen */ diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java index 65e4dcd7c68..ab98ee4bd33 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java @@ -1,12 +1,13 @@ package de.hysky.skyblocker.skyblock.chat; import de.hysky.skyblocker.utils.scheduler.Scheduler; -import java.awt.Color; import java.util.List; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Checkbox; import net.minecraft.client.gui.components.ContainerObjectSelectionList; +import net.minecraft.client.gui.components.StringWidget; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.layouts.LinearLayout; @@ -115,12 +116,14 @@ private class ChatRuleEntry extends AbstractChatRuleEntry { private final ChatRule chatRule; // Widgets + private final StringWidget nameWidget; private final List children; private final LinearLayout layout; @Override public void setX(int x) { super.setX(x); + nameWidget.setX(x + 10); layout.setX(x + 125); layout.arrangeElements(); } @@ -128,6 +131,7 @@ public void setX(int x) { @Override public void setY(int y) { super.setY(y); + nameWidget.setY(y + 8); layout.setY(y); layout.arrangeElements(); } @@ -143,11 +147,18 @@ private ChatRuleEntry(int chatRuleIndex) { this.chatRuleIndex = chatRuleIndex; this.chatRule = ChatRulesHandler.CHAT_RULE_LIST.getData().get(chatRuleIndex); + nameWidget = new StringWidget(Component.literal(chatRule.getName()), minecraft.font); + nameWidget.setMaxWidth(110, StringWidget.TextOverflow.SCROLLING); layout = new LinearLayout(0, 0, LinearLayout.Orientation.HORIZONTAL); - layout.defaultCellSetting().paddingRight(10); - layout.defaultCellSetting().paddingTop(3); + layout.defaultCellSetting().paddingRight(10).paddingTop(3); - Button enabledButton = layout.addChild(Button.builder(enabledButtonText(), this::toggleEnabled).size(50, 20).build()); + Checkbox enabledCheck = layout.addChild(Checkbox.builder(Component.empty(), minecraft.font) + .selected(chatRule.getEnabled()) + .onValueChange((_, value) -> { + hasChanged = true; + chatRule.setEnabled(value); + }) + .build(), s -> s.padding(16, 4, 23, 0)); Button openConfigButton = layout.addChild(Button.builder(Component.translatable("skyblocker.config.chat.chatRules.screen.editRule"), _ -> minecraft.setScreen(new ChatRuleConfigScreen(screen, chatRuleIndex))).size(50, 20).tooltip(Tooltip.create(Component.translatable("skyblocker.config.chat.chatRules.screen.editRule.@Tooltip"))).build()); @@ -155,21 +166,7 @@ private ChatRuleEntry(int chatRuleIndex) { minecraft.setScreen(new ConfirmScreen(this::deleteEntry, Component.translatable("skyblocker.config.chat.chatRules.screen.deleteQuestion"), Component.translatable("skyblocker.config.chat.chatRules.screen.deleteWarning", chatRule.getName()), Component.translatable("selectServer.deleteButton"), CommonComponents.GUI_CANCEL)) ).size(50, 20).build()); - children = List.of(enabledButton, openConfigButton, deleteButton); - } - - private Component enabledButtonText() { - if (chatRule.getEnabled()) { - return Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.true").withColor(Color.GREEN.getRGB()); - } else { - return Component.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.false").withColor(Color.RED.getRGB()); - } - } - - private void toggleEnabled(Button button) { - hasChanged = true; - chatRule.setEnabled(!chatRule.getEnabled()); - button.setMessage(enabledButtonText()); + children = List.of(enabledCheck, openConfigButton, deleteButton); } private void deleteEntry(boolean confirmedAction) { @@ -184,10 +181,10 @@ private void deleteEntry(boolean confirmedAction) { @Override public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) { + // Text + nameWidget.extractRenderState(graphics, mouseX, mouseY, a); // Widgets layout.visitWidgets(child -> child.extractRenderState(graphics, mouseX, mouseY, a)); - // Text - graphics.centeredText(minecraft.font, chatRule.getName(), getX() + 60, this.getY() + 8, CommonColors.WHITE); } @Override diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index ba9a16768d3..a0747be408d 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -106,8 +106,9 @@ "skyblocker.config.chat.chatRules.screen.ruleScreen.outputs": "Outputs", "skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch": "Partial Match", "skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch.@Tooltip": "If the filter can match just part of the chat message.", - "skyblocker.config.chat.chatRules.screen.ruleScreen.regex": "Use regex", + "skyblocker.config.chat.chatRules.screen.ruleScreen.regex": "Use regex (%s)", "skyblocker.config.chat.chatRules.screen.ruleScreen.regex.@Tooltip": "If the filter uses regex or is just plain text.", + "skyblocker.config.chat.chatRules.screen.ruleScreen.regexLink": "?", "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds": "Sound", "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.@Tooltip": "Play a sound when the message is sent.", "skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.amethyst": "Amethyst",