aboutsummaryrefslogtreecommitdiff
path: root/src/client/java/dev/isxander/yacl/gui/controllers/string
diff options
context:
space:
mode:
authorXander <xander@isxander.dev>2022-12-09 16:31:25 +0000
committerGitHub <noreply@github.com>2022-12-09 16:31:25 +0000
commite4856a17133b0567d09cb6db3821674491d57e64 (patch)
tree0c59597708b3ea9f402ba119490537b5c18fdb93 /src/client/java/dev/isxander/yacl/gui/controllers/string
parente1f6d190d862dd86c251fdd5726efe99f8ec1baf (diff)
parent49ff470de36e719d5b963de405de891eca2b69d1 (diff)
downloadYetAnotherConfigLib-e4856a17133b0567d09cb6db3821674491d57e64.tar.gz
YetAnotherConfigLib-e4856a17133b0567d09cb6db3821674491d57e64.tar.bz2
YetAnotherConfigLib-e4856a17133b0567d09cb6db3821674491d57e64.zip
Merge pull request #38 from isXander/update/1.19.3
Diffstat (limited to 'src/client/java/dev/isxander/yacl/gui/controllers/string')
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java44
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java40
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java326
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java105
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java105
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java109
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java109
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java69
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java10
9 files changed, 917 insertions, 0 deletions
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java
new file mode 100644
index 0000000..553e278
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java
@@ -0,0 +1,44 @@
+package dev.isxander.yacl.gui.controllers.string;
+
+import dev.isxander.yacl.api.Controller;
+import dev.isxander.yacl.api.Option;
+import dev.isxander.yacl.api.utils.Dimension;
+import dev.isxander.yacl.gui.AbstractWidget;
+import dev.isxander.yacl.gui.YACLScreen;
+import net.minecraft.text.Text;
+
+/**
+ * A controller that can be any type but can input and output a string.
+ */
+public interface IStringController<T> extends Controller<T> {
+ /**
+ * Gets the option's pending value as a string.
+ *
+ * @see Option#pendingValue()
+ */
+ String getString();
+
+ /**
+ * Sets the option's pending value from a string.
+ *
+ * @see Option#requestSet(Object)
+ */
+ void setFromString(String value);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ default Text formatValue() {
+ return Text.of(getString());
+ }
+
+ default boolean isInputValid(String input) {
+ return true;
+ }
+
+ @Override
+ default AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ return new StringControllerElement(this, screen, widgetDimension, true);
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java
new file mode 100644
index 0000000..3a07641
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java
@@ -0,0 +1,40 @@
+package dev.isxander.yacl.gui.controllers.string;
+
+import dev.isxander.yacl.api.Option;
+import dev.isxander.yacl.api.utils.Dimension;
+import dev.isxander.yacl.gui.AbstractWidget;
+import dev.isxander.yacl.gui.YACLScreen;
+
+/**
+ * A custom text field implementation for strings.
+ */
+public class StringController implements IStringController<String> {
+ private final Option<String> option;
+
+ /**
+ * Constructs a string controller
+ *
+ * @param option bound option
+ */
+ public StringController(Option<String> option) {
+ this.option = option;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Option<String> option() {
+ return option;
+ }
+
+ @Override
+ public String getString() {
+ return option().pendingValue();
+ }
+
+ @Override
+ public void setFromString(String value) {
+ option().requestSet(value);
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java
new file mode 100644
index 0000000..47a4c96
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java
@@ -0,0 +1,326 @@
+package dev.isxander.yacl.gui.controllers.string;
+
+import dev.isxander.yacl.api.utils.Dimension;
+import dev.isxander.yacl.gui.YACLScreen;
+import dev.isxander.yacl.gui.controllers.ControllerWidget;
+import net.minecraft.client.gui.DrawableHelper;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.util.InputUtil;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.function.Consumer;
+
+public class StringControllerElement extends ControllerWidget<IStringController<?>> {
+ protected final boolean instantApply;
+
+ protected String inputField;
+ protected Dimension<Integer> inputFieldBounds;
+ protected boolean inputFieldFocused;
+
+ protected int caretPos;
+ protected int selectionLength;
+
+ protected float ticks;
+
+ private final Text emptyText;
+
+ public StringControllerElement(IStringController<?> control, YACLScreen screen, Dimension<Integer> dim, boolean instantApply) {
+ super(control, screen, dim);
+ this.instantApply = instantApply;
+ inputField = control.getString();
+ inputFieldFocused = false;
+ selectionLength = 0;
+ emptyText = Text.literal("Click to type...").formatted(Formatting.GRAY);
+ control.option().addListener((opt, val) -> inputField = control.getString());
+ setDimension(dim);
+ }
+
+ @Override
+ protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) {
+ ticks += delta;
+
+ String text = getValueText().getString();
+
+ DrawableHelper.fill(matrices, inputFieldBounds.x(), inputFieldBounds.yLimit(), inputFieldBounds.xLimit(), inputFieldBounds.yLimit() + 1, -1);
+ DrawableHelper.fill(matrices, inputFieldBounds.x() + 1, inputFieldBounds.yLimit() + 1, inputFieldBounds.xLimit() + 1, inputFieldBounds.yLimit() + 2, 0xFF404040);
+
+ if (inputFieldFocused || focused) {
+ int caretX = inputFieldBounds.x() + textRenderer.getWidth(text.substring(0, caretPos)) - 1;
+ if (text.isEmpty())
+ caretX += inputFieldBounds.width() / 2;
+
+ if (ticks % 20 <= 10) {
+ DrawableHelper.fill(matrices, caretX, inputFieldBounds.y(), caretX + 1, inputFieldBounds.yLimit(), -1);
+ }
+
+ if (selectionLength != 0) {
+ int selectionX = inputFieldBounds.x() + textRenderer.getWidth(text.substring(0, caretPos + selectionLength));
+ DrawableHelper.fill(matrices, caretX, inputFieldBounds.y() - 1, selectionX, inputFieldBounds.yLimit(), 0x803030FF);
+ }
+ }
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (isAvailable() && getDimension().isPointInside((int) mouseX, (int) mouseY)) {
+ inputFieldFocused = true;
+
+ if (!inputFieldBounds.isPointInside((int) mouseX, (int) mouseY)) {
+ caretPos = getDefaultCarotPos();
+ } else {
+ // gets the appropriate carot position for where you click
+ int textX = (int) mouseX - inputFieldBounds.x();
+ int pos = -1;
+ int currentWidth = 0;
+ for (char ch : inputField.toCharArray()) {
+ pos++;
+ int charLength = textRenderer.getWidth(String.valueOf(ch));
+ if (currentWidth + charLength / 2 > textX) { // if more than half way past the characters select in front of that char
+ caretPos = pos;
+ break;
+ } else if (pos == inputField.length() - 1) {
+ // if we have reached the end and no matches, it must be the second half of the char so the last position
+ caretPos = pos + 1;
+ }
+ currentWidth += charLength;
+ }
+
+ selectionLength = 0;
+ }
+ return true;
+ } else {
+ inputFieldFocused = false;
+ }
+
+ return false;
+ }
+
+ protected int getDefaultCarotPos() {
+ return inputField.length();
+ }
+
+ @Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ if (!inputFieldFocused)
+ return false;
+
+ switch (keyCode) {
+ case InputUtil.GLFW_KEY_ESCAPE, InputUtil.GLFW_KEY_ENTER -> {
+ unfocus();
+ return true;
+ }
+ case InputUtil.GLFW_KEY_LEFT -> {
+ if (Screen.hasShiftDown()) {
+ if (Screen.hasControlDown()) {
+ int spaceChar = findSpaceIndex(true);
+ selectionLength += caretPos - spaceChar;
+ caretPos = spaceChar;
+ } else if (caretPos > 0) {
+ caretPos--;
+ selectionLength += 1;
+ }
+ } else {
+ if (caretPos > 0) {
+ if (selectionLength != 0)
+ caretPos += Math.min(selectionLength, 0);
+ else
+ caretPos--;
+ }
+ selectionLength = 0;
+ }
+
+ return true;
+ }
+ case InputUtil.GLFW_KEY_RIGHT -> {
+ if (Screen.hasShiftDown()) {
+ if (Screen.hasControlDown()) {
+ int spaceChar = findSpaceIndex(false);
+ selectionLength -= spaceChar - caretPos;
+ caretPos = spaceChar;
+ } else if (caretPos < inputField.length()) {
+ caretPos++;
+ selectionLength -= 1;
+ }
+ } else {
+ if (caretPos < inputField.length()) {
+ if (selectionLength != 0)
+ caretPos += Math.max(selectionLength, 0);
+ else
+ caretPos++;
+ }
+ selectionLength = 0;
+ }
+
+ return true;
+ }
+ case InputUtil.GLFW_KEY_BACKSPACE -> {
+ doBackspace();
+ return true;
+ }
+ case InputUtil.GLFW_KEY_DELETE -> {
+ doDelete();
+ return true;
+ }
+ }
+
+ if (canUseShortcuts()) {
+ if (Screen.isPaste(keyCode)) {
+ this.write(client.keyboard.getClipboard());
+ return true;
+ } else if (Screen.isCopy(keyCode) && selectionLength != 0) {
+ client.keyboard.setClipboard(getSelection());
+ return true;
+ } else if (Screen.isCut(keyCode) && selectionLength != 0) {
+ client.keyboard.setClipboard(getSelection());
+ this.write("");
+ return true;
+ } else if (Screen.isSelectAll(keyCode)) {
+ caretPos = inputField.length();
+ selectionLength = -caretPos;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean charTyped(char chr, int modifiers) {
+ if (!inputFieldFocused)
+ return false;
+
+ write(Character.toString(chr));
+
+ return true;
+ }
+
+ protected boolean canUseShortcuts() {
+ return true;
+ }
+
+ protected void doBackspace() {
+ if (selectionLength != 0) {
+ write("");
+ } else if (caretPos > 0) {
+ if (modifyInput(builder -> builder.deleteCharAt(caretPos - 1)))
+ caretPos--;
+ }
+ }
+
+ protected void doDelete() {
+ if (caretPos < inputField.length()) {
+ modifyInput(builder -> builder.deleteCharAt(caretPos));
+ }
+ }
+
+ public void write(String string) {
+ if (selectionLength == 0) {
+ String trimmed = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField));
+
+ if (modifyInput(builder -> builder.insert(caretPos, trimmed))) {
+ caretPos += trimmed.length();
+ }
+ } else {
+ int start = getSelectionStart();
+ int end = getSelectionEnd();
+
+ String trimmed = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField) + textRenderer.getWidth(inputField.substring(start, end)));
+
+ if (modifyInput(builder -> builder.replace(start, end, trimmed))) {
+ caretPos = start + trimmed.length();
+ selectionLength = 0;
+ }
+ }
+ }
+
+ public boolean modifyInput(Consumer<StringBuilder> consumer) {
+ StringBuilder temp = new StringBuilder(inputField);
+ consumer.accept(temp);
+ if (!control.isInputValid(temp.toString()))
+ return false;
+ inputField = temp.toString();
+ if (instantApply)
+ updateControl();
+ return true;
+ }
+
+ public int getMaxLength() {
+ return getDimension().width() / 8 * 7;
+ }
+
+ public int getSelectionStart() {
+ return Math.min(caretPos, caretPos + selectionLength);
+ }
+
+ public int getSelectionEnd() {
+ return Math.max(caretPos, caretPos + selectionLength);
+ }
+
+ protected String getSelection() {
+ return inputField.substring(getSelectionStart(), getSelectionEnd());
+ }
+
+ protected int findSpaceIndex(boolean reverse) {
+ int i;
+ int fromIndex = caretPos;
+ if (reverse) {
+ if (caretPos > 0)
+ fromIndex -= 1;
+ i = this.inputField.lastIndexOf(" ", fromIndex);
+
+ if (i == -1) i = 0;
+ } else {
+ if (caretPos < inputField.length())
+ fromIndex += 1;
+ i = this.inputField.indexOf(" ", fromIndex);
+
+ if (i == -1) i = inputField.length();
+ }
+
+ return i;
+ }
+
+ @Override
+ public boolean changeFocus(boolean lookForwards) {
+ return inputFieldFocused = super.changeFocus(lookForwards);
+ }
+
+ @Override
+ public void unfocus() {
+ super.unfocus();
+ inputFieldFocused = false;
+ if (!instantApply) updateControl();
+ }
+
+ @Override
+ public void setDimension(Dimension<Integer> dim) {
+ super.setDimension(dim);
+
+ int width = Math.max(6, textRenderer.getWidth(getValueText()));
+ inputFieldBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - width, dim.centerY() - textRenderer.fontHeight / 2, width, textRenderer.fontHeight);
+ }
+
+ @Override
+ public boolean isHovered() {
+ return super.isHovered() || inputFieldFocused;
+ }
+
+ protected void updateControl() {
+ control.setFromString(inputField);
+ }
+
+ @Override
+ protected int getHoveredControlWidth() {
+ return getUnhoveredControlWidth();
+ }
+
+ @Override
+ protected Text getValueText() {
+ if (!inputFieldFocused && inputField.isEmpty())
+ return emptyText;
+
+ return instantApply || !inputFieldFocused ? control.formatValue() : Text.of(inputField);
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java
new file mode 100644
index 0000000..8933df3
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java
@@ -0,0 +1,105 @@
+package dev.isxander.yacl.gui.controllers.string.number;
+
+import dev.isxander.yacl.api.Option;
+import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController;
+import net.minecraft.text.Text;
+
+import java.math.BigDecimal;
+import java.util.function.Function;
+
+/**
+ * {@inheritDoc}
+ */
+public class DoubleFieldController extends NumberFieldController<Double> {
+ private final double min, max;
+
+ /**
+ * Constructs a double field controller
+ *
+ * @param option option to bind controller to
+ * @param min minimum allowed value (clamped on apply)
+ * @param max maximum allowed value (clamped on apply)
+ * @param formatter display text, not used whilst editing
+ */
+ public DoubleFieldController(Option<Double> option, double min, double max, Function<Double, Text> formatter) {
+ super(option, formatter);
+ this.min = min;
+ this.max = max;
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Uses {@link DoubleSliderController#DEFAULT_FORMATTER} as display text,
+ * not used whilst editing.
+ *
+ * @param option option to bind controller to
+ * @param min minimum allowed value (clamped on apply)
+ * @param max maximum allowed value (clamped on apply)
+ */
+ public DoubleFieldController(Option<Double> option, double min, double max) {
+ this(option, min, max, DoubleSliderController.DEFAULT_FORMATTER);
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Does not have a minimum or a maximum range.
+ *
+ * @param option option to bind controller to
+ * @param formatter display text, not used whilst editing
+ */
+ public DoubleFieldController(Option<Double> option, Function<Double, Text> formatter) {
+ this(option, -Double.MAX_VALUE, Double.MAX_VALUE, formatter);
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Uses {@link DoubleSliderController#DEFAULT_FORMATTER} as display text,
+ * not used whilst editing.
+ * Does not have a minimum or a maximum range.
+ *
+ * @param option option to bind controller to
+ */
+ public DoubleFieldController(Option<Double> option) {
+ this(option, -Double.MAX_VALUE, Double.MAX_VALUE, DoubleSliderController.DEFAULT_FORMATTER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double min() {
+ return this.min;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double max() {
+ return this.max;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getString() {
+ return String.valueOf(option().pendingValue());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setPendingValue(double value) {
+ option().requestSet(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double pendingValue() {
+ return option().pendingValue();
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java
new file mode 100644
index 0000000..b1eb3a2
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java
@@ -0,0 +1,105 @@
+package dev.isxander.yacl.gui.controllers.string.number;
+
+import dev.isxander.yacl.api.Option;
+import dev.isxander.yacl.gui.controllers.slider.FloatSliderController;
+import net.minecraft.text.Text;
+
+import java.math.BigDecimal;
+import java.util.function.Function;
+
+/**
+ * {@inheritDoc}
+ */
+public class FloatFieldController extends NumberFieldController<Float> {
+ private final float min, max;
+
+ /**
+ * Constructs a double field controller
+ *
+ * @param option option to bind controller to
+ * @param min minimum allowed value (clamped on apply)
+ * @param max maximum allowed value (clamped on apply)
+ * @param formatter display text, not used whilst editing
+ */
+ public FloatFieldController(Option<Float> option, float min, float max, Function<Float, Text> formatter) {
+ super(option, formatter);
+ this.min = min;
+ this.max = max;
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Uses {@link FloatSliderController#DEFAULT_FORMATTER} as display text,
+ * not used whilst editing.
+ *
+ * @param option option to bind controller to
+ * @param min minimum allowed value (clamped on apply)
+ * @param max maximum allowed value (clamped on apply)
+ */
+ public FloatFieldController(Option<Float> option, float min, float max) {
+ this(option, min, max, FloatSliderController.DEFAULT_FORMATTER);
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Does not have a minimum or a maximum range.
+ *
+ * @param option option to bind controller to
+ * @param formatter display text, not used whilst editing
+ */
+ public FloatFieldController(Option<Float> option, Function<Float, Text> formatter) {
+ this(option, -Float.MAX_VALUE, Float.MAX_VALUE, formatter);
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Uses {@link FloatSliderController#DEFAULT_FORMATTER} as display text,
+ * not used whilst editing.
+ * Does not have a minimum or a maximum range.
+ *
+ * @param option option to bind controller to
+ */
+ public FloatFieldController(Option<Float> option) {
+ this(option, -Float.MAX_VALUE, Float.MAX_VALUE, FloatSliderController.DEFAULT_FORMATTER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double min() {
+ return this.min;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double max() {
+ return this.max;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getString() {
+ return String.valueOf(option().pendingValue());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setPendingValue(double value) {
+ option().requestSet((float) value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double pendingValue() {
+ return option().pendingValue();
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java
new file mode 100644
index 0000000..50eecec
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java
@@ -0,0 +1,109 @@
+package dev.isxander.yacl.gui.controllers.string.number;
+
+import dev.isxander.yacl.api.Option;
+import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController;
+import net.minecraft.text.Text;
+
+import java.util.function.Function;
+
+/**
+ * {@inheritDoc}
+ */
+public class IntegerFieldController extends NumberFieldController<Integer> {
+ private final int min, max;
+
+ /**
+ * Constructs a double field controller
+ *
+ * @param option option to bind controller to
+ * @param min minimum allowed value (clamped on apply)
+ * @param max maximum allowed value (clamped on apply)
+ * @param formatter display text, not used whilst editing
+ */
+ public IntegerFieldController(Option<Integer> option, int min, int max, Function<Integer, Text> formatter) {
+ super(option, formatter);
+ this.min = min;
+ this.max = max;
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Uses {@link IntegerSliderController#DEFAULT_FORMATTER} as display text,
+ * not used whilst editing.
+ *
+ * @param option option to bind controller to
+ * @param min minimum allowed value (clamped on apply)
+ * @param max maximum allowed value (clamped on apply)
+ */
+ public IntegerFieldController(Option<Integer> option, int min, int max) {
+ this(option, min, max, IntegerSliderController.DEFAULT_FORMATTER);
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Does not have a minimum or a maximum range.
+ *
+ * @param option option to bind controller to
+ * @param formatter display text, not used whilst editing
+ */
+ public IntegerFieldController(Option<Integer> option, Function<Integer, Text> formatter) {
+ this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, formatter);
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Uses {@link IntegerSliderController#DEFAULT_FORMATTER} as display text,
+ * not used whilst editing.
+ * Does not have a minimum or a maximum range.
+ *
+ * @param option option to bind controller to
+ */
+ public IntegerFieldController(Option<Integer> option) {
+ this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, IntegerSliderController.DEFAULT_FORMATTER);
+ }
+
+ @Override
+ public boolean isInputValid(String input) {
+ return input.matches("\\d+|-|");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double min() {
+ return this.min;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double max() {
+ return this.max;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getString() {
+ return String.valueOf(option().pendingValue());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setPendingValue(double value) {
+ option().requestSet((int) value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double pendingValue() {
+ return option().pendingValue();
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java
new file mode 100644
index 0000000..516de74
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java
@@ -0,0 +1,109 @@
+package dev.isxander.yacl.gui.controllers.string.number;
+
+import dev.isxander.yacl.api.Option;
+import dev.isxander.yacl.gui.controllers.slider.LongSliderController;
+import net.minecraft.text.Text;
+
+import java.util.function.Function;
+
+/**
+ * {@inheritDoc}
+ */
+public class LongFieldController extends NumberFieldController<Long> {
+ private final long min, max;
+
+ /**
+ * Constructs a double field controller
+ *
+ * @param option option to bind controller to
+ * @param min minimum allowed value (clamped on apply)
+ * @param max maximum allowed value (clamped on apply)
+ * @param formatter display text, not used whilst editing
+ */
+ public LongFieldController(Option<Long> option, long min, long max, Function<Long, Text> formatter) {
+ super(option, formatter);
+ this.min = min;
+ this.max = max;
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Uses {@link LongSliderController#DEFAULT_FORMATTER} as display text,
+ * not used whilst editing.
+ *
+ * @param option option to bind controller to
+ * @param min minimum allowed value (clamped on apply)
+ * @param max maximum allowed value (clamped on apply)
+ */
+ public LongFieldController(Option<Long> option, long min, long max) {
+ this(option, min, max, LongSliderController.DEFAULT_FORMATTER);
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Does not have a minimum or a maximum range.
+ *
+ * @param option option to bind controller to
+ * @param formatter display text, not used whilst editing
+ */
+ public LongFieldController(Option<Long> option, Function<Long, Text> formatter) {
+ this(option, -Long.MAX_VALUE, Long.MAX_VALUE, formatter);
+ }
+
+ /**
+ * Constructs a double field controller.
+ * Uses {@link LongSliderController#DEFAULT_FORMATTER} as display text,
+ * not used whilst editing.
+ * Does not have a minimum or a maximum range.
+ *
+ * @param option option to bind controller to
+ */
+ public LongFieldController(Option<Long> option) {
+ this(option, -Long.MAX_VALUE, Long.MAX_VALUE, LongSliderController.DEFAULT_FORMATTER);
+ }
+
+ @Override
+ public boolean isInputValid(String input) {
+ return input.matches("\\d+|-|");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double min() {
+ return this.min;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double max() {
+ return this.max;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getString() {
+ return String.valueOf(option().pendingValue());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setPendingValue(double value) {
+ option().requestSet((long) value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double pendingValue() {
+ return option().pendingValue();
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java
new file mode 100644
index 0000000..bf0354a
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java
@@ -0,0 +1,69 @@
+package dev.isxander.yacl.gui.controllers.string.number;
+
+import dev.isxander.yacl.api.Option;
+import dev.isxander.yacl.api.utils.Dimension;
+import dev.isxander.yacl.gui.AbstractWidget;
+import dev.isxander.yacl.gui.YACLScreen;
+import dev.isxander.yacl.gui.controllers.slider.ISliderController;
+import dev.isxander.yacl.gui.controllers.string.IStringController;
+import dev.isxander.yacl.gui.controllers.string.StringControllerElement;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.MathHelper;
+
+import java.text.DecimalFormatSymbols;
+import java.util.function.Function;
+
+/**
+ * Controller that allows you to enter in numbers using a text field.
+ *
+ * @param <T> number type
+ */
+public abstract class NumberFieldController<T extends Number> implements ISliderController<T>, IStringController<T> {
+ private final Option<T> option;
+ private final Function<T, Text> displayFormatter;
+
+ public NumberFieldController(Option<T> option, Function<T, Text> displayFormatter) {
+ this.option = option;
+ this.displayFormatter = displayFormatter;
+ }
+
+ @Override
+ public Option<T> option() {
+ return this.option;
+ }
+
+ @Override
+ public void setFromString(String value) {
+ if (value.isEmpty() || value.equals(".") || value.equals("-")) value = "0";
+ setPendingValue(MathHelper.clamp(Double.parseDouble(cleanupNumberString(value)), min(), max()));
+ }
+
+ @Override
+ public double pendingValue() {
+ return option().pendingValue().doubleValue();
+ }
+
+ @Override
+ public boolean isInputValid(String input) {
+ return input.matches("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)|[.]||-");
+ }
+
+ @Override
+ public Text formatValue() {
+ return displayFormatter.apply(option().pendingValue());
+ }
+
+ @Override
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ return new StringControllerElement(this, screen, widgetDimension, false);
+ }
+
+ protected String cleanupNumberString(String number) {
+ return number.replace(String.valueOf(DecimalFormatSymbols.getInstance().getGroupingSeparator()), "");
+ }
+
+ @Override
+ public double interval() {
+ return -1;
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java
new file mode 100644
index 0000000..86b9314
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * This package contains implementations of input fields for different number types
+ * <ul>
+ * <li>For doubles: {@link dev.isxander.yacl.gui.controllers.string.number.DoubleFieldController}</li>
+ * <li>For floats: {@link dev.isxander.yacl.gui.controllers.string.number.FloatFieldController}</li>
+ * <li>For integers: {@link dev.isxander.yacl.gui.controllers.string.number.IntegerFieldController}</li>
+ * <li>For longs: {@link dev.isxander.yacl.gui.controllers.string.number.LongFieldController}</li>
+ * </ul>
+ */
+package dev.isxander.yacl.gui.controllers.string.number;