diff --git a/src/main/java/net/earthcomputer/clientcommands/command/TranslateCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/TranslateCommand.java index c345e4da6..21b7c6e7f 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/TranslateCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/TranslateCommand.java @@ -25,6 +25,8 @@ public class TranslateCommand { + public static final String COMMAND_NAME = "ctranslate"; + private static final SimpleCommandExceptionType UNKNOWN_ERROR_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.ctranslate.unknownError")); private static final String URL_FORMAT = "https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=%s&tl=%s&q=%s"; @@ -34,7 +36,7 @@ public class TranslateCommand { private static final Duration DURATION = Duration.ofSeconds(5); public static void register(CommandDispatcher dispatcher) { - dispatcher.register(literal("ctranslate") + dispatcher.register(literal(COMMAND_NAME) .then(argument("query", translationQuery()) .executes(ctx -> translate(ctx.getSource(), getTranslationQuery(ctx, "query"))))); } diff --git a/src/main/java/net/earthcomputer/clientcommands/interfaces/IChatScreen.java b/src/main/java/net/earthcomputer/clientcommands/interfaces/IChatScreen.java new file mode 100644 index 000000000..bc64735cf --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/interfaces/IChatScreen.java @@ -0,0 +1,5 @@ +package net.earthcomputer.clientcommands.interfaces; + +public interface IChatScreen { + boolean clientcommands_isTranslating(); +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/ChatComponent_1Mixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/ChatComponent_1Mixin.java new file mode 100644 index 000000000..3d056609c --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/ChatComponent_1Mixin.java @@ -0,0 +1,57 @@ +package net.earthcomputer.clientcommands.mixin.commands.translate; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.earthcomputer.clientcommands.interfaces.IChatScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ActiveTextCollector; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.TextAlignment; +import net.minecraft.client.gui.components.ChatComponent; +import net.minecraft.client.gui.screens.ChatScreen; +import net.minecraft.client.multiplayer.chat.GuiMessage; +import net.minecraft.client.renderer.state.gui.GuiTextRenderState; +import net.minecraft.util.ARGB; +import net.minecraft.util.FormattedCharSequence; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(targets = "net/minecraft/client/gui/components/ChatComponent$1") +public abstract class ChatComponent_1Mixin { + @WrapOperation(method = "accept", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/ChatComponent$ChatGraphicsAccess;handleMessage(IFLnet/minecraft/util/FormattedCharSequence;)Z")) + private boolean copyText(ChatComponent.ChatGraphicsAccess instance, int textTop, float opacity, FormattedCharSequence text, Operation original, GuiMessage.Line line) { + if (!tryCopy(instance, textTop, text, line)) { + return original.call(instance, textTop, opacity, text); + } + // original.call() always returns false, so we return false here also + return false; + } + + @Unique + private static boolean tryCopy(ChatComponent.ChatGraphicsAccess instance, int textTop, FormattedCharSequence text, GuiMessage.Line line) { + if (!(instance instanceof ChatComponent.ClickableTextOnlyGraphicsAccess clickableTextOnlyGraphicsAccess)) { + return false; + } + Minecraft minecraft = Minecraft.getInstance(); + if (!(minecraft.gui.screen() instanceof ChatScreen chatScreen)) { + return false; + } + if (!((IChatScreen) chatScreen).clientcommands_isTranslating()) { + return false; + } + if (!(clickableTextOnlyGraphicsAccess.output instanceof ActiveTextCollector.ClickableStyleFinder clickableStyleFinder)) { + return false; + } + Font font = minecraft.font; + ActiveTextCollector.Parameters parameters = clickableStyleFinder.defaultParameters(); + int leftX = TextAlignment.LEFT.calculateLeft(0, font, text); + GuiTextRenderState renderState = new GuiTextRenderState(font, text, parameters.pose(), leftX, textTop, ARGB.white(parameters.opacity()), 0, true, true, parameters.scissor()); + boolean[] found = {false}; + ActiveTextCollector.findElementUnderCursor(renderState, clickableStyleFinder.testX, clickableStyleFinder.testY, _ -> { + minecraft.keyboardHandler.setClipboard(line.parent().content().getString()); + found[0] = true; + }); + return found[0]; + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/ChatScreenMixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/ChatScreenMixin.java new file mode 100644 index 000000000..e6b9168a7 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/ChatScreenMixin.java @@ -0,0 +1,34 @@ +package net.earthcomputer.clientcommands.mixin.commands.translate; + +import net.earthcomputer.clientcommands.command.TranslateCommand; +import net.earthcomputer.clientcommands.interfaces.IChatScreen; +import net.minecraft.client.gui.screens.ChatScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ChatScreen.class) +public abstract class ChatScreenMixin extends Screen implements IChatScreen { + + @Unique + private boolean isTranslating = false; + + protected ChatScreenMixin(Component title) { + super(title); + } + + @Inject(method = "onEdited", at = @At("TAIL")) + private void onEdited(String value, CallbackInfo ci) { + this.isTranslating = value.startsWith(Commands.COMMAND_PREFIX + TranslateCommand.COMMAND_NAME); + } + + @Override + public boolean clientcommands_isTranslating() { + return this.isTranslating; + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/package-info.java b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/package-info.java new file mode 100644 index 000000000..88eedf01f --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/translate/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.earthcomputer.clientcommands.mixin.commands.translate; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/resources/clientcommands.aw b/src/main/resources/clientcommands.aw index e220cb5c7..c6bd9c5bf 100644 --- a/src/main/resources/clientcommands.aw +++ b/src/main/resources/clientcommands.aw @@ -49,6 +49,12 @@ accessible field net/minecraft/client/renderer/ShaderManager$CompilationCache co # crelog accessible field net/minecraft/client/gui/screens/DisconnectedScreen parent Lnet/minecraft/client/gui/screens/Screen; +# ctranslate +accessible class net/minecraft/client/gui/components/ChatComponent$ClickableTextOnlyGraphicsAccess +accessible field net/minecraft/client/gui/components/ChatComponent$ClickableTextOnlyGraphicsAccess output Lnet/minecraft/client/gui/ActiveTextCollector; +accessible field net/minecraft/client/gui/ActiveTextCollector$ClickableStyleFinder testX I +accessible field net/minecraft/client/gui/ActiveTextCollector$ClickableStyleFinder testY I + # Game Options accessible field net/minecraft/client/OptionInstance value Ljava/lang/Object; diff --git a/src/main/resources/mixins.clientcommands.json b/src/main/resources/mixins.clientcommands.json index 0f4e72be7..2d4952421 100644 --- a/src/main/resources/mixins.clientcommands.json +++ b/src/main/resources/mixins.clientcommands.json @@ -81,6 +81,8 @@ "commands.reply.ClientPacketListenerMixin", "commands.snap.MinecraftMixin", "commands.time.ClientClockManagerMixin", + "commands.translate.ChatComponent_1Mixin", + "commands.translate.ChatScreenMixin", "dataqueryhandler.ClientPacketListenerMixin", "events.ClientPacketListenerMixin", "events.GuiMixin",