package de.zonlykroks.advancedscripts.screen; import com.mojang.blaze3d.systems.RenderSystem; import de.zonlykroks.advancedscripts.lexer.ScriptColorizer; import de.zonlykroks.advancedscripts.lexer.Token; import de.zonlykroks.advancedscripts.lexer.TokenTypeColors; import net.minecraft.client.font.TextHandler; import net.minecraft.client.gui.DrawableHelper; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.BookScreen; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.widget.PressableWidget; import net.minecraft.client.render.GameRenderer; import net.minecraft.client.util.NarratorManager; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtList; import net.minecraft.nbt.NbtString; import net.minecraft.network.packet.c2s.play.BookUpdateC2SPacket; import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.util.Hand; import org.apache.commons.lang3.mutable.MutableInt; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; public class ScriptEditScreen extends Screen { private PlayerEntity player; private ItemStack itemStack; private Hand hand; private List lines = new ArrayList<>(); private int scroll = 0; private int cursorY = 0; private int cursorX = 0; private int tickCounter; private int keyCode = -1; private long time; public ScriptEditScreen(PlayerEntity player, ItemStack itemStack, Hand hand) { super(NarratorManager.EMPTY); this.player = player; this.itemStack = itemStack; this.hand = hand; NbtCompound nbtCompound = itemStack.getNbt(); if (nbtCompound != null) { BookScreen.filterPages(nbtCompound, s -> { lines.addAll(Arrays.asList(s.split("\n"))); }); } if (lines.isEmpty()) { lines.add(""); } } @Override protected void init() { this.addDrawableChild( new DoneButton(this.width / 2 - 98 / 2, height - 40, () -> { this.client.setScreen(null); finalizeBook(); }) ); } public static final Text DONE = Text.translatable("gui.done"); private class DoneButton extends PressableWidget { private Runnable onPress; public DoneButton(int x, int y, Runnable onPress) { super(x, y, 98, 20, DONE); visible = true; this.onPress = onPress; } @Override public void onPress() { onPress.run(); } @Override protected void appendClickableNarrations(NarrationMessageBuilder builder) { } @Override public boolean isNarratable() { return false; } } @Override public void tick() { super.tick(); ++this.tickCounter; if (keyCode != -1 && System.currentTimeMillis() - time > 500) { key(keyCode); } } @Override public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { this.renderBackground(matrices); RenderSystem.setShader(GameRenderer::getPositionTexProgram); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); fill(matrices, 23, 23, this.width - 23, this.height - 63, TokenTypeColors.BACKGROUND); int lineNumberLength = textRenderer.getWidth(lines.size() + ""); int lineNumberText = scroll + 1; MutableInt lineNumber = new MutableInt(); TextHandler textHandler = this.textRenderer.getTextHandler(); for (int i = scroll; i < lines.size(); i++) { String s = lines.get(i); if (lineNumber.getValue() * 9 + 25 > this.height - 66) { break; } if (s.isEmpty() && i == cursorY) { drawCursor(matrices, 25 + lineNumberLength + 5, lineNumber.getValue() * 9 + 25, true); } // Line number if (lineTooLong(s)) { int height = this.textRenderer.getWrappedLinesHeight(s, this.width - 50 - lineNumberLength - 5); fill(matrices, 25 + lineNumberLength + 2, 25 + lineNumber.getValue() * 9, 25 + lineNumberLength + 3, 25 + lineNumber.getValue() * 9 + height, TokenTypeColors.ERROR); } this.textRenderer.draw(matrices, lineNumberText + "", 25 + lineNumberLength - textRenderer.getWidth(lineNumberText + ""), 25 + lineNumber.getValue() * 9, 0xFFFFFF); lineNumberText++; // Line text List tokens = ScriptColorizer.colorize(lineNumber.getValue(), s); AtomicInteger x = new AtomicInteger(25 + lineNumberLength + 5); AtomicInteger currentXIndex = new AtomicInteger(0); for (Token token : tokens) { int finalI = i; textHandler.wrapLines(token.text, this.width - x.get() - 25, Style.EMPTY, true, (style, start, end) -> { int y = lineNumber.getValue() * 9; if (y + 25 > this.height - 66) { return; } String line = token.text.substring(start, end); int previousXIndex = currentXIndex.get(); currentXIndex.addAndGet(line.length()); if (finalI == cursorY && currentXIndex.get() >= cursorX && previousXIndex <= cursorX) { drawCursor(matrices, x.get() + textRenderer.getWidth(line.substring(0, cursorX - previousXIndex)) - 1, 25 + y, isAtEndOfLine()); } this.textRenderer.draw(matrices, line, x.get(), 25 + y, token.color); x.addAndGet(textRenderer.getWidth(line)); if (x.get() > this.width - 50 - lineNumberLength - 5) { x.set(25 + lineNumberLength + 5); lineNumber.increment(); } }); } lineNumber.increment(); } super.render(matrices, mouseX, mouseY, delta); } private boolean lineTooLong(String s) { if (s.length() >= 1024) { return true; } return textRenderer.getWrappedLinesHeight(s, 114) > 128; } private void drawCursor(MatrixStack matrices, int x, int y, boolean atEnd) { if (this.tickCounter / 6 % 2 == 0) { if (!atEnd) { Objects.requireNonNull(this.textRenderer); DrawableHelper.fill(matrices, x, y - 1, x + 1, y + 9, 0xFFFFFFFF); } else { this.textRenderer.draw(matrices, "_", (float) x, (float) y, 0xFFFFFFFF); } } } private void key(int keyCode) { switch (keyCode) { case 257: case 335: newLine(); break; case 259: backspace(); break; case 261: delete(); break; case 262: moveCursor(1); break; case 263: moveCursor(-1); break; case 264: moveDown(); break; case 265: moveUp(); break; case 268: cursorX = 0; break; case 269: cursorX = lines.get(cursorY).length(); break; } } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (super.keyPressed(keyCode, scanCode, modifiers)) { return true; } key(keyCode); this.keyCode = keyCode; this.time = System.currentTimeMillis(); return true; } @Override public boolean keyReleased(int keyCode, int scanCode, int modifiers) { this.keyCode = -1; return super.keyReleased(keyCode, scanCode, modifiers); } @Override public boolean charTyped(char chr, int modifiers) { if (super.charTyped(chr, modifiers)) { return true; } if (insert(chr, modifiers)) { return true; } return true; } private boolean insert(char chr, int modifiers) { String line = lines.get(cursorY); if (cursorX == line.length()) { line += chr; } else { line = line.substring(0, cursorX) + chr + line.substring(cursorX); } lines.set(cursorY, line); cursorX++; return true; } private boolean newLine() { String line = lines.get(cursorY); String newLine = line.substring(cursorX); line = line.substring(0, cursorX); lines.set(cursorY, line); lines.add(cursorY + 1, newLine); cursorY++; cursorX = 0; return true; } private boolean backspace() { if (cursorX == 0) { if (cursorY == 0) { return true; } String line = lines.get(cursorY); String prevLine = lines.get(cursorY - 1); lines.set(cursorY - 1, prevLine + line); lines.remove(cursorY); cursorY--; cursorX = prevLine.length(); } else { String line = lines.get(cursorY); line = line.substring(0, cursorX - 1) + line.substring(cursorX); lines.set(cursorY, line); cursorX--; } return true; } private boolean delete() { String line = lines.get(cursorY); if (cursorX == line.length()) { if (cursorY == lines.size() - 1) { return true; } String nextLine = lines.get(cursorY + 1); lines.set(cursorY, line + nextLine); lines.remove(cursorY + 1); } else { line = line.substring(0, cursorX) + line.substring(cursorX + 1); lines.set(cursorY, line); } return true; } private boolean moveCursor(int offset) { String line = lines.get(cursorY); if (cursorX + offset < 0) { if (cursorY == 0) { return true; } cursorY--; cursorX = lines.get(cursorY).length(); } else if (cursorX + offset > line.length()) { if (cursorY == lines.size() - 1) { return true; } cursorY++; cursorX = 0; } else { cursorX += offset; } return true; } private boolean moveDown() { if (cursorY == lines.size() - 1) { cursorX = lines.get(cursorY).length(); return true; } cursorY++; cursorX = Math.min(cursorX, lines.get(cursorY).length()); return true; } private boolean moveUp() { if (cursorY == 0) { cursorX = 0; return true; } cursorY--; cursorX = Math.min(cursorX, lines.get(cursorY).length()); return true; } private boolean isAtEndOfLine() { return cursorX == lines.get(cursorY).length(); } @Override public boolean mouseScrolled(double mouseX, double mouseY, double amount) { scroll -= Math.signum(amount); if (scroll > lines.size() - 1) { scroll = lines.size() - 1; } if (scroll < 0) { scroll = 0; } return true; } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { return super.mouseClicked(mouseX, mouseY, button); } private List toPages() { List pages = new ArrayList<>(); StringBuilder page = new StringBuilder(); for (String line : lines) { if (!page.isEmpty()) { page.append("\n"); } if (page.length() + line.length() > 1024) { pages.add(page.toString()); page = new StringBuilder(); } String temp = page + line; if (textRenderer.getWrappedLinesHeight(temp, 114) > 128) { pages.add(page.toString()); page = new StringBuilder(); } if (line.isEmpty()) { page.append("\n"); } else { page.append(line); } } if (!page.isEmpty()) { pages.add(page.toString()); } return pages; } private void finalizeBook() { List pages = toPages(); this.writeNbtData(pages); int i = this.hand == Hand.MAIN_HAND ? this.player.getInventory().selectedSlot : 40; this.client.getNetworkHandler().sendPacket(new BookUpdateC2SPacket(i, pages, Optional.empty())); } private void writeNbtData(List pages) { NbtList nbtList = new NbtList(); pages.stream().map(NbtString::of).forEach(nbtList::add); if (!pages.isEmpty()) { this.itemStack.setSubNbt("pages", nbtList); } } }