[config-ui-tiny] massive editor screen improvement and better positioning

This commit is contained in:
Johannes Frohnmeyer 2023-03-18 13:57:01 +01:00
parent 2607253458
commit 0d800e2a9d
Signed by: Johannes
GPG Key ID: E76429612C2929F4
9 changed files with 122 additions and 47 deletions

View File

@ -9,5 +9,7 @@
"libjf-config-reflect-v1-testmod.jfconfig.enumTest.tooltip": "Enum Test Tooltip",
"libjf-config-reflect-v1-testmod.jfconfig.enum.Test.Test": "Test",
"libjf-config-reflect-v1-testmod.jfconfig.enum.Test.ER": "ER",
"libjf-config-reflect-v1-testmod.jfconfig.moskau": "Moskau"
"libjf-config-reflect-v1-testmod.jfconfig.moskau": "Moskau",
"libjf-config-reflect-v1-testmod.jfconfig.stringList": "String list",
"libjf-config-reflect-v1-testmod.jfconfig.stringList.tooltip": "Tooltip of the String list"
}

View File

@ -10,16 +10,16 @@ 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.screen.ingame.BookScreen;
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.Style;
import net.minecraft.text.Text;
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;
@ -29,12 +29,10 @@ import java.util.*;
import java.util.function.Consumer;
public class EditorScreen extends Screen {
//TODO simple box background
//TODO scrollable
private static final int MAX_TEXT_WIDTH = 114;
private static final int MAX_TEXT_HEIGHT = 128;
private static final int WIDTH = 192;
private static final int HEIGHT = 192;
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;
@ -44,20 +42,50 @@ public class EditorScreen extends Screen {
this::setText,
this::getClipboard,
this::setClipboard,
string -> string.length() < 1024 && this.textRenderer.getWrappedLinesHeight(string, MAX_TEXT_WIDTH) <= MAX_TEXT_HEIGHT
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 Screen parent, @Nullable String text, @Nullable Consumer<String> onSave) {
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;
@ -89,17 +117,15 @@ public class EditorScreen extends Screen {
@Override
protected void init() {
ButtonWidget cancelButton;
ButtonWidget doneButton;
this.invalidatePageContent();
doneButton = this.addDrawableChild(
this.addDrawableChild(
ButtonWidget.builder(ScreenTexts.DONE, button -> quit(true))
.dimensions(this.width / 2 + 2, HEIGHT + 4, 98, 20)
.dimensions(this.width / 2 + 4, height - FOOTER_SIZE + 8, 150, 20)
.build()
);
cancelButton = this.addDrawableChild(
this.addDrawableChild(
ButtonWidget.builder(ScreenTexts.CANCEL, button -> quit(false))
.dimensions(this.width / 2 - 102, HEIGHT + 4, 98, 20)
.dimensions(this.width / 2 - 154, height - FOOTER_SIZE + 8, 150, 20)
.build()
);
}
@ -255,17 +281,44 @@ public class EditorScreen extends Screen {
@Override
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
this.renderBackground(matrices);
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);
RenderSystem.setShaderTexture(0, BookScreen.BOOK_TEXTURE);
int i = (this.width - WIDTH) / 2;
drawTexture(matrices, i, 2, 0, 0, WIDTH, HEIGHT);
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, -16777216);
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);
}
@ -273,7 +326,7 @@ public class EditorScreen extends Screen {
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, -16777216);
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);
}
@ -288,17 +341,17 @@ public class EditorScreen extends Screen {
int j = rect2i.getY();
int k = i + rect2i.getWidth();
int l = j + rect2i.getHeight();
fill(matrices, i, j, k, l, -16776961);
fill(matrices, i, j, k, l, 0xFF0000FF);
}
RenderSystem.disableColorLogicOp();
}
private Position screenPositionToAbsolutePosition(Position position) {
return new Position(position.x - (this.width - WIDTH) / 2 - 36, position.y - 32);
return new Position(position.x - PADDING, position.y - HEADER_SIZE - PADDING + (int)scrollAmount);
}
private Position absolutePositionToScreenPosition(Position position) {
return new Position(position.x + (this.width - WIDTH) / 2 + 36, position.y + 32);
return new Position(position.x + PADDING, position.y + HEADER_SIZE + PADDING - (int)scrollAmount);
}
@Override
@ -306,9 +359,10 @@ public class EditorScreen extends Screen {
if (super.mouseClicked(mouseX, mouseY, button)) {
return true;
}
if (button == 0) {
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)));
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()) {
@ -338,13 +392,31 @@ public class EditorScreen extends Screen {
return true;
}
if (button == 0) {
int i = this.getPageContent().getCursorPosition(this.textRenderer, this.screenPositionToAbsolutePosition(new Position((int)mouseX, (int)mouseY)));
this.currentPageSelectionManager.moveCursorTo(i, true);
this.invalidatePageContent();
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();
@ -368,7 +440,7 @@ public class EditorScreen extends Screen {
MutableInt lineIndexM = new MutableInt();
MutableBoolean mutableBoolean = new MutableBoolean();
TextHandler textHandler = this.textRenderer.getTextHandler();
textHandler.wrapLines(content, MAX_TEXT_WIDTH, Style.EMPTY, true, (style, start, end) -> {
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"));

View File

@ -6,6 +6,7 @@ import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.EntryListWidget;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.WidgetState;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.presets.PresetsScreen;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@ -127,7 +128,7 @@ public class TinyConfigScreen extends Screen {
this.renderBackground(matrices);
this.placeholder.render(matrices, mouseX, mouseY, delta);
drawCenteredTextWithShadow(matrices, textRenderer, title, width / 2, 15, 0xFFFFFF);
drawCenteredTextWithShadow(matrices, textRenderer, title, width / 2, 16 - textRenderer.fontHeight, 0xFFFFFF);
Optional<Text> hovered = placeholder.getChild().getHoveredEntryTitle(mouseY);
if (hovered.isPresent()) {

View File

@ -3,8 +3,7 @@ package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.v1.ui.tiny.WidgetFactory;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.EntryInfoWidgetBuilder;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.EntryListWidget;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.*;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.presets.PresetsScreen;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.ScreenRect;
@ -31,8 +30,7 @@ public class TinyConfigTab implements Tab {
widget.updateCache();
}
this.list = new EntryListWidget(screen.getClient(), textRenderer, screen.width, screen.height, 32, screen.height - 32, 25);
if (screen.hasClient() && screen.getClient().world != null) this.list.setRenderBackground(false);
this.list = new EntryListWidget(screen.getClient(), textRenderer, screen.width, screen.height, 32, screen.height - 36, 25);
if (isRoot) {
for (Map.Entry<String, ConfigCategory> entry : config.getCategories().entrySet()) {

View File

@ -3,24 +3,22 @@ package io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry;
import io.gitlab.jfronny.commons.ref.R;
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders;
import io.gitlab.jfronny.commons.throwable.Try;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
import io.gitlab.jfronny.libjf.config.api.v1.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.v1.type.Type;
import io.gitlab.jfronny.libjf.config.api.v1.ui.tiny.WidgetFactory;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.EditorScreen;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.WidgetState;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Language;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
@ -177,8 +175,10 @@ public class EntryInfoWidgetBuilder {
} else {
jsonified = state.tempValue;
}
String key = config.getTranslationPrefix() + info.getName();
screen.getClient().setScreen(new EditorScreen(
Text.translatable(config.getTranslationPrefix() + info.getName()),
Text.translatable(key),
I18n.hasTranslation(key + ".tooltip") ? Text.translatable(key + ".tooltip") : null,
screen,
jsonified,
json -> {

View File

@ -21,10 +21,11 @@ import java.util.function.Supplier;
public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEntry> {
TextRenderer textRenderer;
public EntryListWidget(MinecraftClient minecraftClient, TextRenderer tr, int i, int j, int k, int l, int m) {
super(minecraftClient, i, j, k, l, m);
public EntryListWidget(MinecraftClient client, TextRenderer tr, int i, int j, int k, int l, int m) {
super(client, i, j, k, l, m);
this.centerListVertically = false;
textRenderer = tr;
setRenderBackground(client.world == null);
}
@Override

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
package io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v1.EntryInfo;

View File

@ -12,6 +12,7 @@ import java.util.List;
public class PresetListWidget extends ElementListWidget<PresetListWidget.PresetEntry> {
public PresetListWidget(MinecraftClient client, int i, int j, int k, int l, int m) {
super(client, i, j, k, l, m);
setRenderBackground(client.world == null);
}
public void addButton(ClickableWidget button) {

View File

@ -53,7 +53,7 @@ public class PresetsScreen extends Screen {
this.renderBackground(matrices);
this.list.render(matrices, mouseX, mouseY, delta);
drawCenteredTextWithShadow(matrices, textRenderer, title, width / 2, 15, 0xFFFFFF);
drawCenteredTextWithShadow(matrices, textRenderer, title, width / 2, 16 - textRenderer.fontHeight / 2, 0xFFFFFF);
super.render(matrices, mouseX, mouseY, delta);
}