From 01481bb4dfbb8bab2f7e9af58a30eda00723068a Mon Sep 17 00:00:00 2001 From: JFronny Date: Tue, 14 Mar 2023 18:25:49 +0100 Subject: [PATCH] [config-ui-tiny] preliminary json editing support --- .../libjf-config-ui-tiny-v1/lang/en_us.json | 3 +- .../config/impl/ui/tiny/EditorScreen.java | 505 ++++++++++++++++++ .../config/impl/ui/tiny/WidgetState.java | 3 +- .../ui/tiny/entry/EntryInfoWidgetBuilder.java | 60 ++- .../libjf-config-ui-tiny-v1/lang/en_us.json | 4 + 5 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/EditorScreen.java create mode 100644 libjf-config-ui-tiny-v1/src/client/resources/assets/libjf-config-ui-tiny-v1/lang/en_us.json diff --git a/libjf-config-core-v1/src/client/resources/assets/libjf-config-ui-tiny-v1/lang/en_us.json b/libjf-config-core-v1/src/client/resources/assets/libjf-config-ui-tiny-v1/lang/en_us.json index 916669a..c30f9b2 100644 --- a/libjf-config-core-v1/src/client/resources/assets/libjf-config-ui-tiny-v1/lang/en_us.json +++ b/libjf-config-core-v1/src/client/resources/assets/libjf-config-ui-tiny-v1/lang/en_us.json @@ -4,5 +4,6 @@ "libjf-config-v1.see-also": "See also: %s", "libjf-config-v1.reset": "Reset", "libjf-config-core-v1.no-screen": "No screen", - "libjf-config-core-v1.no-screen.description": "No mod for rendering config UIs was discovered. Install LibJF to add one." + "libjf-config-core-v1.no-screen.description": "No mod for rendering config UIs was discovered. Install LibJF to add one.", + "libjf-config-core-v1.edit": "Edit" } \ No newline at end of file diff --git a/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/EditorScreen.java b/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/EditorScreen.java new file mode 100644 index 0000000..0b9614c --- /dev/null +++ b/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/EditorScreen.java @@ -0,0 +1,505 @@ +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.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.util.Util; +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 { + //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 int tickCounter; + private final String initialText; + private String text = ""; + private final SelectionManager currentPageSelectionManager = new SelectionManager( + this::getText, + this::setText, + this::getClipboard, + this::setClipboard, + string -> string.length() < 1024 && this.textRenderer.getWrappedLinesHeight(string, MAX_TEXT_WIDTH) <= MAX_TEXT_HEIGHT + ); + + private long lastClickTime; + private int lastClickIndex = -1; + + @Nullable + private PageContent pageContent = PageContent.EMPTY; + private final Screen parent; + private final Consumer onSave; + + public EditorScreen(@Nullable Text title, @Nullable Screen parent, @Nullable String text, @Nullable Consumer onSave) { + super(title == null ? NarratorManager.EMPTY : title); + this.parent = parent; + 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() { + ButtonWidget cancelButton; + ButtonWidget doneButton; + this.invalidatePageContent(); + doneButton = this.addDrawableChild( + ButtonWidget.builder(ScreenTexts.DONE, button -> quit(true)) + .dimensions(this.width / 2 + 2, HEIGHT + 4, 98, 20) + .build() + ); + cancelButton = this.addDrawableChild( + ButtonWidget.builder(ScreenTexts.CANCEL, button -> quit(false)) + .dimensions(this.width / 2 - 102, HEIGHT + 4, 98, 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) { + this.renderBackground(matrices); + this.setFocused(null); + RenderSystem.setShaderTexture(0, BookScreen.BOOK_TEXTURE); + int i = (this.width - WIDTH) / 2; + drawTexture(matrices, i, 2, 0, 0, WIDTH, HEIGHT); + PageContent pageContent = this.getPageContent(); + for (Line line : pageContent.lines) { + this.textRenderer.draw(matrices, line.text, line.x, line.y, -16777216); + } + this.drawSelection(matrices, pageContent.selectionRectangles); + this.drawCursor(matrices, pageContent.position, pageContent.atEnd); + 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, -16777216); + } 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, -16776961); + } + RenderSystem.disableColorLogicOp(); + } + + private Position screenPositionToAbsolutePosition(Position position) { + return new Position(position.x - (this.width - WIDTH) / 2 - 36, position.y - 32); + } + + private Position absolutePositionToScreenPosition(Position position) { + return new Position(position.x + (this.width - WIDTH) / 2 + 36, position.y + 32); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) { + return true; + } + if (button == 0) { + 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) { + int i = this.getPageContent().getCursorPosition(this.textRenderer, this.screenPositionToAbsolutePosition(new Position((int)mouseX, (int)mouseY))); + this.currentPageSelectionManager.moveCursorTo(i, true); + this.invalidatePageContent(); + } + 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 list = Lists.newArrayList(); + 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) -> { + 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 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) { } +} diff --git a/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/WidgetState.java b/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/WidgetState.java index 3cba779..fb96d4d 100644 --- a/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/WidgetState.java +++ b/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/WidgetState.java @@ -16,6 +16,7 @@ public class WidgetState { public Text error; public boolean inLimits = true; public String tempValue; + public boolean managedTemp = true; public T cachedValue; @Nullable public WidgetFactory factory; @@ -37,7 +38,7 @@ public class WidgetState { public void updateCache(T newValue) { cachedValue = newValue; - tempValue = newValue == null ? null : newValue.toString(); + if (managedTemp) tempValue = newValue == null ? null : newValue.toString(); } public void writeToEntry() throws IllegalAccessException { diff --git a/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/entry/EntryInfoWidgetBuilder.java b/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/entry/EntryInfoWidgetBuilder.java index 87f4c59..ce8e108 100644 --- a/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/entry/EntryInfoWidgetBuilder.java +++ b/libjf-config-ui-tiny-v1/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/entry/EntryInfoWidgetBuilder.java @@ -1,19 +1,26 @@ 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.toast.SystemToast; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import java.io.StringWriter; import java.util.LinkedList; import java.util.List; import java.util.function.Function; @@ -75,8 +82,8 @@ public class EntryInfoWidgetBuilder { } }); } else { - LibJf.LOGGER.error("Unsupported entry type in " + info.getName() + ": " + type.getName() + " - not displaying config control"); - factory = null; + LibJf.LOGGER.error("Unsupported entry type in " + info.getName() + ": " + type.getName() + " - displaying fallback"); + factory = jsonScreen(config, info, state); } Try.orThrow(() -> state.initialize(info, knownStates, factory, config.getTranslationPrefix())); @@ -147,6 +154,55 @@ public class EntryInfoWidgetBuilder { }; } + private static WidgetFactory jsonScreen(ConfigCategory config, EntryInfo info, WidgetState state) { + state.managedTemp = false; + state.tempValue = null; + return (screen, textRenderer) -> new WidgetFactory.Widget( + R::nop, + ButtonWidget.builder(Text.translatable("libjf-config-core-v1.edit"), button -> { + final String jsonified; + if (state.tempValue == null) { + try { + jsonified = GsonHolders.CONFIG.getGson().toJson(state.cachedValue); + } catch (Throwable e) { + LibJf.LOGGER.error("Could not stringify element", e); + SystemToast.add( + screen.getClient().getToastManager(), + SystemToast.Type.PACK_LOAD_FAILURE, + Text.translatable("libjf-config-ui-tiny-v1.entry.json.read.fail.title"), + Text.translatable("libjf-config-ui-tiny-v1.entry.json.read.fail.description") + ); + return; + } + } else { + jsonified = state.tempValue; + } + screen.getClient().setScreen(new EditorScreen( + Text.translatable(config.getTranslationPrefix() + info.getName()), + screen, + jsonified, + json -> { + try { + state.updateCache(GsonHolders.CONFIG.getGson().fromJson(json, info.getValueType().asClass())); + state.tempValue = null; + } catch (Throwable e) { + LibJf.LOGGER.error("Could not write element", e); + SystemToast.add( + screen.getClient().getToastManager(), + SystemToast.Type.PACK_LOAD_FAILURE, + Text.translatable("libjf-config-ui-tiny-v1.entry.json.write.fail.title"), + Text.translatable("libjf-config-ui-tiny-v1.entry.json.write.fail.description") + ); + state.tempValue = json; + } + } + )); + }) + .dimensions(screen.width - 110, 0, info.getWidth(), 20) + .build() + ); + } + private static WidgetFactory slider(EntryInfo info, WidgetState state, Function t2d, Function d2t, boolean wholeNumber) { double min = info.getMinValue(); double max = info.getMaxValue(); diff --git a/libjf-config-ui-tiny-v1/src/client/resources/assets/libjf-config-ui-tiny-v1/lang/en_us.json b/libjf-config-ui-tiny-v1/src/client/resources/assets/libjf-config-ui-tiny-v1/lang/en_us.json new file mode 100644 index 0000000..e33841b --- /dev/null +++ b/libjf-config-ui-tiny-v1/src/client/resources/assets/libjf-config-ui-tiny-v1/lang/en_us.json @@ -0,0 +1,4 @@ +{ + "libjf-config-ui-tiny-v1.entry.json.read.fail.title": "Could not read", + "libjf-config-ui-tiny-v1.entry.json.read.fail.description": "The given entry could not be stringified. Please edit the config manually" +} \ No newline at end of file