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

181 lines
7.7 KiB
Java

package io.gitlab.jfronny.chattransform.client.mixin;
import io.gitlab.jfronny.chattransform.*;
import io.gitlab.jfronny.chattransform.client.*;
import net.minecraft.client.font.TextRenderer;
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;
@Shadow @Final private TextRenderer textRenderer;
@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 {
transformStart.clear();
}
}
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) {
transformStart.clear();
lastSubstitution = null;
}
@Unique private final TransformStart transformStart = new TransformStart();
@Unique private Substitution lastSubstitution = null;
void transform() {
if (!transformStart.isAvailable()) transformStart.set(selectionStart - 1);
if (transformStart.get() >= selectionStart) return;
String currentString = getText().substring(transformStart.get(), selectionStart);
Set<Map.Entry<String, String>> complete = getStartingWith(currentString);
// Exact match
if (Cfg.substitutions.containsKey(currentString) && complete.size() == 1
&& substitute(transformStart.get(), selectionStart, Cfg.substitutions.get(currentString))) {
transformStart.set(selectionStart);
return;
}
if (complete.isEmpty()) {
if (transformStart.get() == selectionStart - 1) {
// Nothing starts with this char
transformStart.increment();
} else {
// Something previously started with this...
String previousString = getText().substring(transformStart.get(), selectionStart - 1);
if (Cfg.substitutions.containsKey(previousString)
// ...and matched -> replace
&& substitute(transformStart.get(), 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)
transformStart.increment();
transform();
}
}
}
}
@Override
public String chattransform$finalize() {
String str = getText();
if (!transformStart.isAvailable() || transformStart.get() >= selectionStart) return str;
String currentString = str.substring(transformStart.get(), selectionStart);
if (!Cfg.substitutions.containsKey(currentString)
|| !substitute(transformStart.get(), selectionStart, Cfg.substitutions.get(currentString))) {
transformStart.increment();
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 += newLen - oldLen;
else if (selectionStart > start) selectionStart = start + newLen;
selectionEnd = selectionStart;
lastSubstitution = new Substitution(start, start + newLen);
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() && Cfg.visualize) {
int y = this.drawsBackground ? this.getY() + (this.height - 8) / 2 : this.getY();
if (transformStart.isAvailable()) {
int x = getCharacterX(transformStart.get());
chattransform$fill(matrices, x, y - 1, x + 1, y + 1 + 9, 0x7f0000ff);
}
if (transformStart.showPrevious()) {
int x = getCharacterX(transformStart.getPrevious());
chattransform$fill(matrices, x, y - 1, x + 1, y + 1 + 9, 0x7fff0000);
}
if (lastSubstitution != null && lastSubstitution.shouldShow()) {
int start = getCharacterX(lastSubstitution.start());
int end = getCharacterX(Math.min(lastSubstitution.end() + 1, text.length()));
chattransform$fill(matrices, start, y - 1, end, y + 1 + 9, 0x7fffff00);
}
}
}
@Unique
private void chattransform$fill(MatrixStack matrices, int x1, int y1, int x2, int y2, int color) {
if (x1 < 0) x1 = 0;
if (x2 < 0) x2 = 0;
int maxX = getX() + getWidth();
if (x1 > maxX) x1 = maxX;
if (x2 > maxX) x2 = maxX;
if (x1 < x2) {
int i = x1;
x1 = x2;
x2 = i;
}
if (y1 < y2) {
int i = y1;
y1 = y2;
y2 = i;
}
textRenderer.draw(matrices, "X={" + x1 + ";" + x2 + "} Y={" + y1 + ";" + y2 + "}", 0, 0, 0xFF000000);
fill(matrices, x1, y1, x2, y2, color);
}
}