578 lines
22 KiB
Java
578 lines
22 KiB
Java
package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.mojang.blaze3d.platform.GlStateManager;
|
|
import com.mojang.blaze3d.systems.RenderSystem;
|
|
import io.gitlab.jfronny.commons.ref.R;
|
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.client.font.TextHandler;
|
|
import net.minecraft.client.font.TextRenderer;
|
|
import net.minecraft.client.gui.DrawableHelper;
|
|
import net.minecraft.client.gui.screen.Screen;
|
|
import net.minecraft.client.gui.widget.ButtonWidget;
|
|
import net.minecraft.client.util.NarratorManager;
|
|
import net.minecraft.client.util.SelectionManager;
|
|
import net.minecraft.client.util.math.MatrixStack;
|
|
import net.minecraft.client.util.math.Rect2i;
|
|
import net.minecraft.screen.ScreenTexts;
|
|
import net.minecraft.text.*;
|
|
import net.minecraft.util.Formatting;
|
|
import net.minecraft.util.Util;
|
|
import net.minecraft.util.math.MathHelper;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.mutable.MutableBoolean;
|
|
import org.apache.commons.lang3.mutable.MutableInt;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.util.*;
|
|
import java.util.function.Consumer;
|
|
|
|
public class EditorScreen extends Screen {
|
|
private static final int SCROLLBAR_SIZE = 7;
|
|
private static final int HEADER_SIZE = 32;
|
|
private static final int FOOTER_SIZE = 36;
|
|
private static final int PADDING = 14;
|
|
|
|
private int tickCounter;
|
|
private final String initialText;
|
|
private String text = "";
|
|
private final SelectionManager currentPageSelectionManager = new SelectionManager(
|
|
this::getText,
|
|
this::setText,
|
|
this::getClipboard,
|
|
this::setClipboard,
|
|
string -> true
|
|
);
|
|
|
|
private long lastClickTime;
|
|
private int lastClickIndex = -1;
|
|
|
|
private double scrollAmount;
|
|
private boolean scrolling;
|
|
|
|
private int getViewportHeight() {
|
|
return height - HEADER_SIZE - FOOTER_SIZE - PADDING * 2;
|
|
}
|
|
|
|
private int getViewportWidth() {
|
|
return width - SCROLLBAR_SIZE - PADDING * 2;
|
|
}
|
|
|
|
private int getMaxScroll() {
|
|
return Math.max(0, getMaxPosition() - getViewportHeight());
|
|
}
|
|
|
|
private int getMaxPosition() {
|
|
return getPageContent().lines.length * textRenderer.fontHeight;
|
|
}
|
|
|
|
private int getScrollbarPositionX() {
|
|
return width - 7;
|
|
}
|
|
|
|
private void setScrollAmount(double amount) {
|
|
this.scrollAmount = MathHelper.clamp(amount, 0, getMaxScroll());
|
|
invalidatePageContent();
|
|
}
|
|
|
|
@Nullable
|
|
private PageContent pageContent = PageContent.EMPTY;
|
|
private final Screen parent;
|
|
private final Consumer<String> onSave;
|
|
@Nullable private final Text subtitle;
|
|
|
|
public EditorScreen(@Nullable Text title, @Nullable Text subtitle, @Nullable Screen parent, @Nullable String text, @Nullable Consumer<String> onSave) {
|
|
super(title == null ? NarratorManager.EMPTY : title);
|
|
this.parent = parent;
|
|
this.subtitle = subtitle == null ? null : subtitle.copy().formatted(Formatting.GRAY);
|
|
this.initialText = text;
|
|
if (text != null) this.text = text;
|
|
this.onSave = onSave == null ? R::nop : onSave;
|
|
}
|
|
|
|
public void setText(String text) {
|
|
this.text = Objects.requireNonNull(text);
|
|
}
|
|
|
|
public String getText() {
|
|
return this.text;
|
|
}
|
|
|
|
private void setClipboard(String clipboard) {
|
|
if (this.client != null) {
|
|
SelectionManager.setClipboard(this.client, clipboard);
|
|
}
|
|
}
|
|
|
|
private String getClipboard() {
|
|
return this.client != null ? SelectionManager.getClipboard(this.client) : "";
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
++this.tickCounter;
|
|
}
|
|
|
|
@Override
|
|
protected void init() {
|
|
this.invalidatePageContent();
|
|
this.addDrawableChild(
|
|
ButtonWidget.builder(ScreenTexts.DONE, button -> quit(true))
|
|
.dimensions(this.width / 2 + 4, height - FOOTER_SIZE + 8, 150, 20)
|
|
.build()
|
|
);
|
|
this.addDrawableChild(
|
|
ButtonWidget.builder(ScreenTexts.CANCEL, button -> quit(false))
|
|
.dimensions(this.width / 2 - 154, height - FOOTER_SIZE + 8, 150, 20)
|
|
.build()
|
|
);
|
|
}
|
|
|
|
private void quit(boolean save) {
|
|
if (save && (this.initialText == null || !this.initialText.equals(this.text))) onSave.accept(text);
|
|
close();
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
Objects.requireNonNull(client).setScreen(parent);
|
|
}
|
|
|
|
@Override
|
|
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
|
if (super.keyPressed(keyCode, scanCode, modifiers)) {
|
|
return true;
|
|
}
|
|
boolean bl = this.keyPressedEditMode(keyCode);
|
|
if (bl) {
|
|
this.invalidatePageContent();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean charTyped(char chr, int modifiers) {
|
|
if (super.charTyped(chr, modifiers)) {
|
|
return true;
|
|
}
|
|
if (SharedConstants.isValidChar(chr)) {
|
|
this.currentPageSelectionManager.insert(Character.toString(chr));
|
|
this.invalidatePageContent();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean keyPressedEditMode(int keyCode) {
|
|
boolean ctrl = Screen.hasControlDown() && !Screen.hasShiftDown() && !Screen.hasAltDown();
|
|
SelectionManager.SelectionType selectionType = Screen.hasControlDown() ? SelectionManager.SelectionType.WORD : SelectionManager.SelectionType.CHARACTER;
|
|
return switch (keyCode) {
|
|
case 65 -> {
|
|
if (ctrl) {
|
|
this.currentPageSelectionManager.selectAll();
|
|
yield true;
|
|
}
|
|
yield false;
|
|
}
|
|
case 67 -> {
|
|
if (ctrl) {
|
|
this.currentPageSelectionManager.copy();
|
|
yield true;
|
|
}
|
|
yield false;
|
|
}
|
|
case 83 -> {
|
|
if (ctrl) {
|
|
quit(true);
|
|
yield true;
|
|
}
|
|
yield false;
|
|
}
|
|
case 86 -> {
|
|
if (ctrl) {
|
|
this.currentPageSelectionManager.paste();
|
|
yield true;
|
|
}
|
|
yield false;
|
|
}
|
|
case 88 -> {
|
|
if (ctrl) {
|
|
this.currentPageSelectionManager.cut();
|
|
yield true;
|
|
}
|
|
yield false;
|
|
}
|
|
case 259 -> {
|
|
this.currentPageSelectionManager.delete(-1, selectionType);
|
|
yield true;
|
|
}
|
|
case 261 -> {
|
|
this.currentPageSelectionManager.delete(1, selectionType);
|
|
yield true;
|
|
}
|
|
case 257, 335 -> {
|
|
this.currentPageSelectionManager.insert("\n");
|
|
yield true;
|
|
}
|
|
case 263 -> {
|
|
this.currentPageSelectionManager.moveCursor(-1, Screen.hasShiftDown(), selectionType);
|
|
yield true;
|
|
}
|
|
case 262 -> {
|
|
this.currentPageSelectionManager.moveCursor(1, Screen.hasShiftDown(), selectionType);
|
|
yield true;
|
|
}
|
|
case 265 -> {
|
|
this.moveUpLine();
|
|
yield true;
|
|
}
|
|
case 264 -> {
|
|
this.moveDownLine();
|
|
yield true;
|
|
}
|
|
case 268 -> {
|
|
this.moveToLineStart();
|
|
yield true;
|
|
}
|
|
case 269 -> {
|
|
this.moveToLineEnd();
|
|
yield true;
|
|
}
|
|
default -> false;
|
|
};
|
|
}
|
|
|
|
private void moveUpLine() {
|
|
this.moveVertically(-1);
|
|
}
|
|
|
|
private void moveDownLine() {
|
|
this.moveVertically(1);
|
|
}
|
|
|
|
private void moveVertically(int lines) {
|
|
int i = this.currentPageSelectionManager.getSelectionStart();
|
|
int j = this.getPageContent().getVerticalOffset(i, lines);
|
|
this.currentPageSelectionManager.moveCursorTo(j, Screen.hasShiftDown());
|
|
}
|
|
|
|
private void moveToLineStart() {
|
|
if (Screen.hasControlDown()) {
|
|
this.currentPageSelectionManager.moveCursorToStart(Screen.hasShiftDown());
|
|
} else {
|
|
int i = this.currentPageSelectionManager.getSelectionStart();
|
|
int j = this.getPageContent().getLineStart(i);
|
|
this.currentPageSelectionManager.moveCursorTo(j, Screen.hasShiftDown());
|
|
}
|
|
}
|
|
|
|
private void moveToLineEnd() {
|
|
if (Screen.hasControlDown()) {
|
|
this.currentPageSelectionManager.moveCursorToEnd(Screen.hasShiftDown());
|
|
} else {
|
|
int i = this.currentPageSelectionManager.getSelectionStart();
|
|
int j = this.getPageContent().getLineEnd(i);
|
|
this.currentPageSelectionManager.moveCursorTo(j, Screen.hasShiftDown());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
|
renderBackground(matrices);
|
|
if (subtitle == null) {
|
|
drawCenteredTextWithShadow(matrices, textRenderer, title, width / 2, (HEADER_SIZE - textRenderer.fontHeight) / 2, 0xFFFFFF);
|
|
} else {
|
|
drawCenteredTextWithShadow(matrices, textRenderer, title, width / 2, HEADER_SIZE / 2 - textRenderer.fontHeight, 0xFFFFFF);
|
|
drawCenteredTextWithShadow(matrices, textRenderer, subtitle, width / 2, HEADER_SIZE / 2, 0xFFFFFF);
|
|
}
|
|
this.setFocused(null);
|
|
|
|
if (client.world == null) {
|
|
RenderSystem.setShaderTexture(0, DrawableHelper.OPTIONS_BACKGROUND_TEXTURE);
|
|
RenderSystem.setShaderColor(0.125f, 0.125f, 0.125f, 1.0f);
|
|
drawTexture(matrices, 0, HEADER_SIZE, width - SCROLLBAR_SIZE, height - FOOTER_SIZE + (int)scrollAmount, width - SCROLLBAR_SIZE, height - HEADER_SIZE - FOOTER_SIZE, 32, 32);
|
|
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
}
|
|
|
|
enableScissor(0, HEADER_SIZE, width - SCROLLBAR_SIZE, height - FOOTER_SIZE);
|
|
PageContent pageContent = this.getPageContent();
|
|
for (Line line : pageContent.lines) {
|
|
this.textRenderer.draw(matrices, line.text, line.x, line.y, 0xFFFFFFFF);
|
|
}
|
|
this.drawSelection(matrices, pageContent.selectionRectangles);
|
|
this.drawCursor(matrices, pageContent.position, pageContent.atEnd);
|
|
disableScissor();
|
|
|
|
int i = this.getScrollbarPositionX();
|
|
int j = i + 6;
|
|
int m;
|
|
if ((m = this.getMaxScroll()) > 0) {
|
|
int n = (getViewportHeight() * getViewportHeight()) / getMaxPosition();
|
|
n = MathHelper.clamp(n, 32, getViewportHeight() - 8);
|
|
int o = (int)scrollAmount * (getViewportHeight() - n) / m + HEADER_SIZE;
|
|
if (o < HEADER_SIZE) o = HEADER_SIZE;
|
|
fill(matrices, i, HEADER_SIZE, j, height - FOOTER_SIZE, 0xFF000000);
|
|
fill(matrices, i, o, j, o + n, 0xFF808080);
|
|
fill(matrices, i, o, j - 1, o + n - 1, 0xFFC0C0C0);
|
|
}
|
|
|
|
super.render(matrices, mouseX, mouseY, delta);
|
|
}
|
|
|
|
private void drawCursor(MatrixStack matrices, Position position, boolean atEnd) {
|
|
if (this.tickCounter / 6 % 2 == 0) {
|
|
position = this.absolutePositionToScreenPosition(position);
|
|
if (!atEnd) {
|
|
DrawableHelper.fill(matrices, position.x, position.y - 1, position.x + 1, position.y + this.textRenderer.fontHeight, 0xFFFFFFFF);
|
|
} else {
|
|
this.textRenderer.draw(matrices, "_", position.x, position.y, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void drawSelection(MatrixStack matrices, Rect2i[] selectionRectangles) {
|
|
RenderSystem.enableColorLogicOp();
|
|
RenderSystem.logicOp(GlStateManager.LogicOp.OR_REVERSE);
|
|
for (Rect2i rect2i : selectionRectangles) {
|
|
int i = rect2i.getX();
|
|
int j = rect2i.getY();
|
|
int k = i + rect2i.getWidth();
|
|
int l = j + rect2i.getHeight();
|
|
fill(matrices, i, j, k, l, 0xFF0000FF);
|
|
}
|
|
RenderSystem.disableColorLogicOp();
|
|
}
|
|
|
|
private Position screenPositionToAbsolutePosition(Position position) {
|
|
return new Position(position.x - PADDING, position.y - HEADER_SIZE - PADDING + (int)scrollAmount);
|
|
}
|
|
|
|
private Position absolutePositionToScreenPosition(Position position) {
|
|
return new Position(position.x + PADDING, position.y + HEADER_SIZE + PADDING - (int)scrollAmount);
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
if (super.mouseClicked(mouseX, mouseY, button)) {
|
|
return true;
|
|
}
|
|
scrolling = button == 0 && mouseX >= getScrollbarPositionX();
|
|
if (button == 0 && !scrolling) {
|
|
long l = Util.getMeasuringTimeMs();
|
|
int i = this.getPageContent().getCursorPosition(this.textRenderer, this.screenPositionToAbsolutePosition(new Position((int) mouseX, (int) mouseY)));
|
|
if (i >= 0) {
|
|
if (i == this.lastClickIndex && l - this.lastClickTime < 250L) {
|
|
if (!this.currentPageSelectionManager.isSelecting()) {
|
|
this.selectCurrentWord(i);
|
|
} else {
|
|
this.currentPageSelectionManager.selectAll();
|
|
}
|
|
} else {
|
|
this.currentPageSelectionManager.moveCursorTo(i, Screen.hasShiftDown());
|
|
}
|
|
this.invalidatePageContent();
|
|
}
|
|
this.lastClickIndex = i;
|
|
this.lastClickTime = l;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void selectCurrentWord(int cursor) {
|
|
String string = this.getText();
|
|
this.currentPageSelectionManager.setSelection(TextHandler.moveCursorByWords(string, -1, cursor, false), TextHandler.moveCursorByWords(string, 1, cursor, false));
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
|
if (super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) {
|
|
return true;
|
|
}
|
|
if (button == 0) {
|
|
if (scrolling) {
|
|
if (mouseY < HEADER_SIZE) setScrollAmount(0);
|
|
else if (mouseY > height - FOOTER_SIZE) setScrollAmount(getMaxScroll());
|
|
else {
|
|
double d = Math.max(1, getMaxScroll());
|
|
int i = getViewportHeight();
|
|
int j = MathHelper.clamp((int)((float)(i * i) / (float)this.getMaxPosition()), 32, i - 8);
|
|
double e = Math.max(1d, d / (i - j));
|
|
setScrollAmount(scrollAmount + deltaY * e);
|
|
}
|
|
} else {
|
|
int i = this.getPageContent().getCursorPosition(this.textRenderer, this.screenPositionToAbsolutePosition(new Position((int)mouseX, (int)mouseY)));
|
|
this.currentPageSelectionManager.moveCursorTo(i, true);
|
|
this.invalidatePageContent();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
|
|
setScrollAmount(scrollAmount - amount * textRenderer.fontHeight * 2);
|
|
return true;
|
|
}
|
|
|
|
private PageContent getPageContent() {
|
|
if (this.pageContent == null) {
|
|
this.pageContent = this.createPageContent();
|
|
}
|
|
return this.pageContent;
|
|
}
|
|
|
|
private void invalidatePageContent() {
|
|
this.pageContent = null;
|
|
}
|
|
|
|
private PageContent createPageContent() {
|
|
String content = this.getText();
|
|
if (content.isEmpty()) {
|
|
return PageContent.EMPTY;
|
|
}
|
|
int i = this.currentPageSelectionManager.getSelectionStart();
|
|
int j = this.currentPageSelectionManager.getSelectionEnd();
|
|
IntArrayList intList = new IntArrayList();
|
|
ArrayList<Line> list = Lists.newArrayList();
|
|
MutableInt lineIndexM = new MutableInt();
|
|
MutableBoolean mutableBoolean = new MutableBoolean();
|
|
TextHandler textHandler = this.textRenderer.getTextHandler();
|
|
textHandler.wrapLines(content, getViewportWidth(), Style.EMPTY, true, (style, start, end) -> {
|
|
int lineIndex = lineIndexM.getAndIncrement();
|
|
String string = content.substring(start, end);
|
|
mutableBoolean.setValue(string.endsWith("\n"));
|
|
String string2 = StringUtils.stripEnd(string, " \n");
|
|
int y = lineIndex * this.textRenderer.fontHeight;
|
|
Position position = this.absolutePositionToScreenPosition(new Position(0, y));
|
|
intList.add(start);
|
|
list.add(new Line(style, string2, position.x, position.y));
|
|
});
|
|
int[] is = intList.toIntArray();
|
|
boolean bl = i == content.length();
|
|
int l;
|
|
Position position;
|
|
if (bl && mutableBoolean.isTrue()) {
|
|
position = new Position(0, list.size() * this.textRenderer.fontHeight);
|
|
} else {
|
|
int k = getLineFromOffset(is, i);
|
|
l = this.textRenderer.getWidth(content.substring(is[k], i));
|
|
position = new Position(l, k * this.textRenderer.fontHeight);
|
|
}
|
|
ArrayList<Rect2i> list2 = Lists.newArrayList();
|
|
if (i != j) {
|
|
int o;
|
|
l = Math.min(i, j);
|
|
int m = Math.max(i, j);
|
|
int n = getLineFromOffset(is, l);
|
|
if (n == (o = getLineFromOffset(is, m))) {
|
|
int p = n * this.textRenderer.fontHeight;
|
|
int q = is[n];
|
|
list2.add(this.getLineSelectionRectangle(content, textHandler, l, m, p, q));
|
|
} else {
|
|
int p = n + 1 > is.length ? content.length() : is[n + 1];
|
|
list2.add(this.getLineSelectionRectangle(content, textHandler, l, p, n * this.textRenderer.fontHeight, is[n]));
|
|
for (int q = n + 1; q < o; ++q) {
|
|
int r = q * this.textRenderer.fontHeight;
|
|
String string2 = content.substring(is[q], is[q + 1]);
|
|
int s = (int)textHandler.getWidth(string2);
|
|
list2.add(this.getRectFromCorners(new Position(0, r), new Position(s, r + this.textRenderer.fontHeight)));
|
|
}
|
|
list2.add(this.getLineSelectionRectangle(content, textHandler, is[o], m, o * this.textRenderer.fontHeight, is[o]));
|
|
}
|
|
}
|
|
return new PageContent(content, position, bl, is, list.toArray(new Line[0]), list2.toArray(new Rect2i[0]));
|
|
}
|
|
|
|
static int getLineFromOffset(int[] lineStarts, int position) {
|
|
int i = Arrays.binarySearch(lineStarts, position);
|
|
if (i < 0) {
|
|
return -(i + 2);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
private Rect2i getLineSelectionRectangle(String string, TextHandler handler, int selectionStart, int selectionEnd, int lineY, int lineStart) {
|
|
String string2 = string.substring(lineStart, selectionStart);
|
|
String string3 = string.substring(lineStart, selectionEnd);
|
|
Position position = new Position((int)handler.getWidth(string2), lineY);
|
|
Position position2 = new Position((int)handler.getWidth(string3), lineY + this.textRenderer.fontHeight);
|
|
return this.getRectFromCorners(position, position2);
|
|
}
|
|
|
|
private Rect2i getRectFromCorners(Position start, Position end) {
|
|
Position position = this.absolutePositionToScreenPosition(start);
|
|
Position position2 = this.absolutePositionToScreenPosition(end);
|
|
int i = Math.min(position.x, position2.x);
|
|
int j = Math.max(position.x, position2.x);
|
|
int k = Math.min(position.y, position2.y);
|
|
int l = Math.max(position.y, position2.y);
|
|
return new Rect2i(i, k, j - i, l - k);
|
|
}
|
|
|
|
static class PageContent {
|
|
static final PageContent EMPTY = new PageContent("", new Position(0, 0), true, new int[]{0}, new Line[]{new Line(Style.EMPTY, "", 0, 0)}, new Rect2i[0]);
|
|
private final String pageContent;
|
|
final Position position;
|
|
final boolean atEnd;
|
|
private final int[] lineStarts;
|
|
final Line[] lines;
|
|
final Rect2i[] selectionRectangles;
|
|
|
|
public PageContent(String pageContent, Position position, boolean atEnd, int[] lineStarts, Line[] lines, Rect2i[] selectionRectangles) {
|
|
this.pageContent = pageContent;
|
|
this.position = position;
|
|
this.atEnd = atEnd;
|
|
this.lineStarts = lineStarts;
|
|
this.lines = lines;
|
|
this.selectionRectangles = selectionRectangles;
|
|
}
|
|
|
|
public int getCursorPosition(TextRenderer renderer, Position position) {
|
|
int i = position.y / renderer.fontHeight;
|
|
if (i < 0) {
|
|
return 0;
|
|
}
|
|
if (i >= this.lines.length) {
|
|
return this.pageContent.length();
|
|
}
|
|
Line line = this.lines[i];
|
|
return this.lineStarts[i] + renderer.getTextHandler().getTrimmedLength(line.content, position.x, line.style);
|
|
}
|
|
|
|
public int getVerticalOffset(int position, int lines) {
|
|
int m;
|
|
int i = getLineFromOffset(this.lineStarts, position);
|
|
int j = i + lines;
|
|
if (0 <= j && j < this.lineStarts.length) {
|
|
int k = position - this.lineStarts[i];
|
|
int l = this.lines[j].content.length();
|
|
m = this.lineStarts[j] + Math.min(k, l);
|
|
} else {
|
|
m = position;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
public int getLineStart(int position) {
|
|
int i = getLineFromOffset(this.lineStarts, position);
|
|
return this.lineStarts[i];
|
|
}
|
|
|
|
public int getLineEnd(int position) {
|
|
int i = getLineFromOffset(this.lineStarts, position);
|
|
return this.lineStarts[i] + this.lines[i].content.length();
|
|
}
|
|
}
|
|
|
|
record Line(Style style, String content, Text text, int x, int y) {
|
|
public Line(Style style, String content, int x, int y) {
|
|
this(style, content, Text.literal(content).setStyle(style), x, y);
|
|
}
|
|
}
|
|
|
|
record Position(int x, int y) { }
|
|
}
|