Chat-Transform/src/client/java/io/gitlab/jfronny/chattransform/mixin/TextFieldWidgetMixin.java

142 lines
6.2 KiB
Java

package io.gitlab.jfronny.chattransform.mixin;
import io.gitlab.jfronny.chattransform.*;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.*;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Mixin(TextFieldWidget.class)
public abstract class TextFieldWidgetMixin extends ClickableWidget implements ITextFieldWidget {
public TextFieldWidgetMixin(int x, int y, int width, int height, Text message) {
super(x, y, width, height, message);
}
@Shadow public abstract String getText();
@Shadow private String text;
@Shadow private int selectionStart;
@Shadow private int selectionEnd;
@Shadow private boolean drawsBackground;
@Shadow public abstract int getCharacterX(int index);
@Shadow public abstract boolean isVisible();
@Shadow public abstract void setCursor(int cursor);
@Shadow private Predicate<String> textPredicate;
@Unique private boolean chattransform$active = false;
@Override
public void chattransform$activate() {
chattransform$active = true;
ChatTransform.LOG.info("Activated widget " + this);
}
@Unique private boolean chattransform$shouldTransform = false;
@Redirect(method = "write(Ljava/lang/String;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/widget/TextFieldWidget;text:Ljava/lang/String;", opcode = Opcodes.PUTFIELD))
void transformBeforeWrite(TextFieldWidget instance, String value, String tx) {
if (chattransform$active) {
if (selectionStart == selectionEnd && tx.length() == 1 && !Screen.hasAltDown() && !getText().startsWith("/")) {
chattransform$shouldTransform = true;
} else {
chattransform$start = null;
}
}
this.text = value;
}
@Inject(method = "write(Ljava/lang/String;)V", at = @At("TAIL"))
void transformAfterWrite(String text, CallbackInfo ci) {
if (chattransform$shouldTransform) {
chattransform$shouldTransform = false;
transform();
}
}
@Inject(method = "setCursor(I)V", at = @At("TAIL"))
void updateStartOnSetCursor(int cursor, CallbackInfo ci) {
chattransform$start = null;
}
@Unique private Integer chattransform$start = null;
void transform() {
if (chattransform$start == null) chattransform$start = selectionStart - 1;
if (chattransform$start < 0) chattransform$start = 0;
if (chattransform$start >= selectionStart) return;
String currentString = getText().substring(chattransform$start, selectionStart);
Set<Map.Entry<String, String>> complete = getStartingWith(currentString);
// Exact match
if (Cfg.substitutions.containsKey(currentString) && complete.size() == 1
&& substitute(chattransform$start, selectionStart, Cfg.substitutions.get(currentString))) {
chattransform$start = selectionStart;
return;
}
if (complete.isEmpty()) {
if (chattransform$start == selectionStart - 1) {
// Nothing starts with this char
chattransform$start++;
} else {
// Something previously started with this...
String previousString = getText().substring(chattransform$start, selectionStart - 1);
if (Cfg.substitutions.containsKey(previousString)
// ...and matched -> replace
&& substitute(chattransform$start, selectionStart - 1, Cfg.substitutions.get(previousString))) {
setCursor(selectionStart + 1);
} else {
// ...and didn't match -> move transform start and call transform again (substring might have matched)
chattransform$start++;
transform();
}
}
}
}
@Override
public String chattransform$finalize() {
String str = getText();
if (chattransform$start == null || chattransform$start >= selectionStart) return str;
String currentString = str.substring(chattransform$start, selectionStart);
if (!Cfg.substitutions.containsKey(currentString)
|| !substitute(chattransform$start, selectionStart, Cfg.substitutions.get(currentString))) {
chattransform$start++;
return chattransform$finalize();
}
return str;
}
boolean substitute(int start, int end, String substitution) {
ChatTransform.LOG.info("Transforming " + getText().substring(start, end) + " to " + substitution);
String sub = text.substring(0, start) + substitution + text.substring(end);
if (textPredicate.test(sub)) {
this.text = sub;
int oldLen = end - start;
int newLen = substitution.length();
if (selectionStart > end) selectionStart -= oldLen + newLen;
else if (selectionStart > start) selectionStart = start + newLen;
selectionEnd = selectionStart;
return true;
} else return false;
}
Set<Map.Entry<String, String>> getStartingWith(String start) {
return Cfg.substitutions.entrySet().stream().filter(s -> s.getKey().startsWith(start)).collect(Collectors.toUnmodifiableSet());
}
@Inject(method = "renderButton(Lnet/minecraft/client/util/math/MatrixStack;IIF)V", at = @At(value = "TAIL"))
void renderTransformStart(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (isVisible() && chattransform$start != null) {
int x = getCharacterX(chattransform$start);
int y = this.drawsBackground ? this.getY() + (this.height - 8) / 2 : this.getY();
DrawableHelper.fill(matrices, x, y - 1, x + 1, y + 1 + 9, 0x7f0000ff);
}
}
}