From d163b9128d760e53e34fd6c08dbf782fa3d50c51 Mon Sep 17 00:00:00 2001 From: isXander Date: Sun, 27 Nov 2022 18:17:36 +0000 Subject: split sourcesets --- .../controllers/slider/DoubleSliderController.java | 114 +++++++++++++++ .../controllers/slider/FloatSliderController.java | 114 +++++++++++++++ .../gui/controllers/slider/ISliderController.java | 54 +++++++ .../slider/IntegerSliderController.java | 111 ++++++++++++++ .../controllers/slider/LongSliderController.java | 111 ++++++++++++++ .../slider/SliderControllerElement.java | 160 +++++++++++++++++++++ .../yacl/gui/controllers/slider/package-info.java | 10 ++ 7 files changed, 674 insertions(+) create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java (limited to 'src/client/java/dev/isxander/yacl/gui/controllers/slider') diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java new file mode 100644 index 0000000..b530e8c --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java @@ -0,0 +1,114 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for doubles. + */ +public class DoubleSliderController implements ISliderController { + /** + * Formats doubles to two decimal places + */ + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.2f", value)); + + private final Option option; + + private final double min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for doubles + * using the default value formatter {@link DoubleSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public DoubleSliderController(Option option, double min, double max, double interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for doubles. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Text} + */ + public DoubleSliderController(Option option, double min, double max, double interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@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/slider/FloatSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java new file mode 100644 index 0000000..d7c203e --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java @@ -0,0 +1,114 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for floats. + */ +public class FloatSliderController implements ISliderController { + /** + * Formats floats to one decimal place + */ + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.1f", value)); + + private final Option option; + + private final float min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for floats + * using the default value formatter {@link FloatSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public FloatSliderController(Option option, float min, float max, float interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for floats. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Text} + */ + public FloatSliderController(Option option, float min, float max, float interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@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/slider/ISliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java new file mode 100644 index 0000000..aa3c18f --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java @@ -0,0 +1,54 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; + +/** + * Simple custom slider implementation that shifts the current value across when shown. + *

+ * For simplicity, {@link SliderControllerElement} works in doubles so each + * {@link ISliderController} must cast to double. This is to get around re-writing the element for every type. + */ +public interface ISliderController extends Controller { + /** + * Gets the minimum value for the slider + */ + double min(); + + /** + * Gets the maximum value for the slider + */ + double max(); + + /** + * Gets the interval (or step size) for the slider. + */ + double interval(); + + /** + * Gets the range of the slider. + */ + default double range() { + return max() - min(); + } + + /** + * Sets the {@link dev.isxander.yacl.api.Option}'s pending value + */ + void setPendingValue(double value); + + /** + * Gets the {@link dev.isxander.yacl.api.Option}'s pending value + */ + double pendingValue(); + + /** + * {@inheritDoc} + */ + @Override + default AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new SliderControllerElement(this, screen, widgetDimension, min(), max(), interval()); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java new file mode 100644 index 0000000..a8bca7c --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java @@ -0,0 +1,111 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for integers. + */ +public class IntegerSliderController implements ISliderController { + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); + + private final Option option; + + private final int min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for integers + * using the default value formatter {@link IntegerSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public IntegerSliderController(Option option, int min, int max, int interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for integers. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Text} + */ + public IntegerSliderController(Option option, int min, int max, int interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@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/slider/LongSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java new file mode 100644 index 0000000..50559d5 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java @@ -0,0 +1,111 @@ +package dev.isxander.yacl.gui.controllers.slider; + +import dev.isxander.yacl.api.Option; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; + +import java.util.function.Function; + +/** + * {@link ISliderController} for longs. + */ +public class LongSliderController implements ISliderController { + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); + + private final Option option; + + private final long min, max, interval; + + private final Function valueFormatter; + + /** + * Constructs a {@link ISliderController} for longs + * using the default value formatter {@link LongSliderController#DEFAULT_FORMATTER}. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + */ + public LongSliderController(Option option, long min, long max, long interval) { + this(option, min, max, interval, DEFAULT_FORMATTER); + } + + /** + * Constructs a {@link ISliderController} for longs. + * + * @param option bound option + * @param min minimum slider value + * @param max maximum slider value + * @param interval step size (or increments) for the slider + * @param valueFormatter format the value into any {@link Text} + */ + public LongSliderController(Option option, long min, long max, long interval, Function valueFormatter) { + Validate.isTrue(max > min, "`max` cannot be smaller than `min`"); + Validate.isTrue(interval > 0, "`interval` must be more than 0"); + Validate.notNull(valueFormatter, "`valueFormatter` must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.valueFormatter = valueFormatter; + } + + /** + * {@inheritDoc} + */ + @Override + public Option option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * {@inheritDoc} + */ + @Override + public double min() { + return min; + } + + /** + * {@inheritDoc} + */ + @Override + public double max() { + return max; + } + + /** + * {@inheritDoc} + */ + @Override + public double interval() { + return interval; + } + + /** + * {@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/slider/SliderControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java new file mode 100644 index 0000000..913cc00 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java @@ -0,0 +1,160 @@ +package dev.isxander.yacl.gui.controllers.slider; + +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.math.MatrixStack; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.glfw.GLFW; + +public class SliderControllerElement extends ControllerWidget> { + private final double min, max, interval; + + private float interpolation; + + private Dimension sliderBounds; + + private boolean mouseDown = false; + + public SliderControllerElement(ISliderController option, YACLScreen screen, Dimension dim, double min, double max, double interval) { + super(option, screen, dim); + this.min = min; + this.max = max; + this.interval = interval; + setDimension(dim); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + super.render(matrices, mouseX, mouseY, delta); + + calculateInterpolation(); + } + + @Override + protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { + // track + DrawableHelper.fill(matrices, sliderBounds.x(), sliderBounds.centerY() - 1, sliderBounds.xLimit(), sliderBounds.centerY(), -1); + // track shadow + DrawableHelper.fill(matrices, sliderBounds.x() + 1, sliderBounds.centerY(), sliderBounds.xLimit() + 1, sliderBounds.centerY() + 1, 0xFF404040); + + // thumb shadow + DrawableHelper.fill(matrices, getThumbX() - getThumbWidth() / 2 + 1, sliderBounds.y() + 1, getThumbX() + getThumbWidth() / 2 + 1, sliderBounds.yLimit() + 1, 0xFF404040); + // thumb + DrawableHelper.fill(matrices, getThumbX() - getThumbWidth() / 2, sliderBounds.y(), getThumbX() + getThumbWidth() / 2, sliderBounds.yLimit(), -1); + } + + @Override + protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { + matrices.push(); + if (isHovered()) + matrices.translate(-(sliderBounds.width() + 6 + getThumbWidth() / 2f), 0, 0); + super.drawValueText(matrices, mouseX, mouseY, delta); + matrices.pop(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isAvailable() || button != 0 || !sliderBounds.isPointInside((int) mouseX, (int) mouseY)) + return false; + + mouseDown = true; + + setValueFromMouse(mouseX); + return true; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (!isAvailable() || button != 0 || !mouseDown) + return false; + + setValueFromMouse(mouseX); + return true; + } + + public void incrementValue(double amount) { + control.setPendingValue(MathHelper.clamp(control.pendingValue() + interval * amount, min, max)); + calculateInterpolation(); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + if (!isAvailable() || (!isMouseOver(mouseX, mouseY)) || (!Screen.hasShiftDown() && !Screen.hasControlDown())) + return false; + + incrementValue(amount); + return true; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (isAvailable() && mouseDown) + playDownSound(); + mouseDown = false; + + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) + return false; + + switch (keyCode) { + case GLFW.GLFW_KEY_LEFT, GLFW.GLFW_KEY_DOWN -> incrementValue(-1); + case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> incrementValue(1); + default -> { + return false; + } + } + + return true; + } + + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + return super.isMouseOver(mouseX, mouseY) || mouseDown; + } + + protected void setValueFromMouse(double mouseX) { + double value = (mouseX - sliderBounds.x()) / sliderBounds.width() * control.range(); + control.setPendingValue(roundToInterval(value)); + calculateInterpolation(); + } + + protected double roundToInterval(double value) { + return MathHelper.clamp(min + (interval * Math.round(value / interval)), min, max); // extremely imprecise, requires clamping + } + + @Override + protected int getHoveredControlWidth() { + return sliderBounds.width() + getUnhoveredControlWidth() + 6 + getThumbWidth() / 2; + } + + protected void calculateInterpolation() { + interpolation = MathHelper.clamp((float) ((control.pendingValue() - control.min()) * 1 / control.range()), 0f, 1f); + } + + @Override + public void setDimension(Dimension dim) { + super.setDimension(dim); + sliderBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - getThumbWidth() / 2 - dim.width() / 3, dim.centerY() - 5, dim.width() / 3, 10); + } + + protected int getThumbX() { + return (int) (sliderBounds.x() + sliderBounds.width() * interpolation); + } + + protected int getThumbWidth() { + return 4; + } + + @Override + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (super.isMouseOver(mouseX, mouseY)) + super.postRender(matrices, mouseX, mouseY, delta); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java new file mode 100644 index 0000000..bff0d57 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java @@ -0,0 +1,10 @@ +/** + * This package contains implementations of sliders for different number types + *

    + *
  • For doubles: {@link dev.isxander.yacl.gui.controllers.slider.DoubleSliderController}
  • + *
  • For floats: {@link dev.isxander.yacl.gui.controllers.slider.FloatSliderController}
  • + *
  • For integers: {@link dev.isxander.yacl.gui.controllers.slider.IntegerSliderController}
  • + *
  • For longs: {@link dev.isxander.yacl.gui.controllers.slider.LongSliderController}
  • + *
+ */ +package dev.isxander.yacl.gui.controllers.slider; -- cgit From daf3168671bc9c0cd64693d7129cddfdb1a08bb2 Mon Sep 17 00:00:00 2001 From: isXander Date: Sat, 3 Dec 2022 18:57:03 +0000 Subject: number fields + StringControllerElement improvements when highlighted text, arrow keys go to each side of the selection switch to InputUtil instead of GLFW bump yarn --- build.gradle.kts | 2 +- .../yacl/gui/controllers/ActionController.java | 4 +- .../yacl/gui/controllers/BooleanController.java | 3 +- .../yacl/gui/controllers/ColorController.java | 21 +++-- .../yacl/gui/controllers/TickBoxController.java | 4 +- .../cycling/CyclingControllerElement.java | 8 +- .../controllers/slider/DoubleSliderController.java | 2 +- .../controllers/slider/FloatSliderController.java | 2 +- .../slider/IntegerSliderController.java | 2 +- .../controllers/slider/LongSliderController.java | 2 +- .../slider/SliderControllerElement.java | 6 +- .../gui/controllers/string/IStringController.java | 12 +++ .../gui/controllers/string/StringController.java | 5 - .../string/StringControllerElement.java | 91 +++++++++++------- .../string/number/DoubleFieldController.java | 105 +++++++++++++++++++++ .../string/number/FloatFieldController.java | 105 +++++++++++++++++++++ .../string/number/IntegerFieldController.java | 104 ++++++++++++++++++++ .../string/number/LongFieldController.java | 104 ++++++++++++++++++++ .../string/number/NumberFieldController.java | 69 ++++++++++++++ .../controllers/string/number/package-info.java | 10 ++ .../java/dev/isxander/yacl/test/GuiTest.java | 43 +++++++++ .../dev/isxander/yacl/test/config/ConfigData.java | 4 + 22 files changed, 644 insertions(+), 64 deletions(-) create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java create mode 100644 src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java (limited to 'src/client/java/dev/isxander/yacl/gui/controllers/slider') diff --git a/build.gradle.kts b/build.gradle.kts index 4a47fb0..1ece3df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -64,7 +64,7 @@ val fabricLoaderVersion: String by project dependencies { minecraft("com.mojang:minecraft:$minecraftVersion") - mappings("net.fabricmc:yarn:$minecraftVersion+build.1:v2") + mappings("net.fabricmc:yarn:$minecraftVersion+build.2:v2") modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion") "modClientImplementation"(fabricApi.module("fabric-resource-loader-v0", "0.68.1+1.19.3")) diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java b/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java index b8e2cd1..7666dff 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java @@ -5,8 +5,8 @@ import dev.isxander.yacl.api.Controller; import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.AbstractWidget; import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.util.InputUtil; import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; import java.util.function.BiConsumer; @@ -94,7 +94,7 @@ public class ActionController implements Controller { return false; } - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { + if (keyCode == InputUtil.GLFW_KEY_ENTER || keyCode == InputUtil.GLFW_KEY_SPACE || keyCode == InputUtil.GLFW_KEY_KP_ENTER) { toggleSetting(); return true; } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java b/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java index a68475d..6853a03 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java @@ -108,7 +108,7 @@ public class ColorController implements IStringController { private final List allowedChars; public ColorControllerElement(ColorController control, YACLScreen screen, Dimension dim) { - super(control, screen, dim); + super(control, screen, dim, true); this.colorController = control; this.allowedChars = ImmutableList.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); } @@ -135,21 +135,22 @@ public class ColorController implements IStringController { if (caretPos == 0) return; - string = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); + String trimmed = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); - inputField.replace(caretPos, caretPos + string.length(), string); - caretPos += string.length(); - setSelectionLength(); - - updateControl(); + if (modifyInput(builder -> builder.replace(caretPos, caretPos + trimmed.length(), trimmed))) { + caretPos += trimmed.length(); + setSelectionLength(); + updateControl(); + } } @Override protected void doBackspace() { if (caretPos > 1) { - inputField.setCharAt(caretPos - 1, '0'); - caretPos--; - updateControl(); + if (modifyInput(builder -> builder.setCharAt(caretPos - 1, '0'))) { + caretPos--; + updateControl(); + } } } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java b/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java index 340983d..b0ae449 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java @@ -6,9 +6,9 @@ import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.AbstractWidget; import dev.isxander.yacl.gui.YACLScreen; import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; /** * This controller renders a tickbox @@ -109,7 +109,7 @@ public class TickBoxController implements Controller { return false; } - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_SPACE || keyCode == GLFW.GLFW_KEY_KP_ENTER) { + if (keyCode == InputUtil.GLFW_KEY_ENTER || keyCode == InputUtil.GLFW_KEY_SPACE || keyCode == InputUtil.GLFW_KEY_KP_ENTER) { toggleSetting(); return true; } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java index ab0a9c3..246fbec 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java @@ -4,7 +4,7 @@ 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.screen.Screen; -import org.lwjgl.glfw.GLFW; +import net.minecraft.client.util.InputUtil; public class CyclingControllerElement extends ControllerWidget> { @@ -39,11 +39,11 @@ public class CyclingControllerElement extends ControllerWidget + case InputUtil.GLFW_KEY_LEFT, InputUtil.GLFW_KEY_DOWN -> cycleValue(-1); - case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> + case InputUtil.GLFW_KEY_RIGHT, InputUtil.GLFW_KEY_UP -> cycleValue(1); - case GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_SPACE, GLFW.GLFW_KEY_KP_ENTER -> + case InputUtil.GLFW_KEY_ENTER, InputUtil.GLFW_KEY_SPACE, InputUtil.GLFW_KEY_KP_ENTER -> cycleValue(Screen.hasControlDown() || Screen.hasShiftDown() ? -1 : 1); default -> { return false; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java index b530e8c..54c7476 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java @@ -13,7 +13,7 @@ public class DoubleSliderController implements ISliderController { /** * Formats doubles to two decimal places */ - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.2f", value)); + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.2f", value).replaceAll("[\u00a0\u202F]", " ")); private final Option option; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java index d7c203e..84ca9a2 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java @@ -13,7 +13,7 @@ public class FloatSliderController implements ISliderController { /** * Formats floats to one decimal place */ - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.1f", value)); + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,.1f", value).replaceAll("[\u00a0\u202F]", " ")); private final Option option; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java index a8bca7c..50ec9d2 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java @@ -10,7 +10,7 @@ import java.util.function.Function; * {@link ISliderController} for integers. */ public class IntegerSliderController implements ISliderController { - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value).replaceAll("[\u00a0\u202F]", " ")); private final Option option; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java index 50559d5..3038402 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java @@ -10,7 +10,7 @@ import java.util.function.Function; * {@link ISliderController} for longs. */ public class LongSliderController implements ISliderController { - public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value)); + public static final Function DEFAULT_FORMATTER = value -> Text.of(String.format("%,d", value).replaceAll("[\u00a0\u202F]", " ")); private final Option option; diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java b/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java index 913cc00..c78e0eb 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java @@ -5,9 +5,9 @@ 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.util.math.MathHelper; -import org.lwjgl.glfw.GLFW; public class SliderControllerElement extends ControllerWidget> { private final double min, max, interval; @@ -104,8 +104,8 @@ public class SliderControllerElement extends ControllerWidget incrementValue(-1); - case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> incrementValue(1); + case InputUtil.GLFW_KEY_LEFT, InputUtil.GLFW_KEY_DOWN -> incrementValue(-1); + case InputUtil.GLFW_KEY_RIGHT, InputUtil.GLFW_KEY_UP -> incrementValue(1); default -> { return false; } 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 index 41843b8..553e278 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java @@ -2,6 +2,9 @@ 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; /** @@ -29,4 +32,13 @@ public interface IStringController extends Controller { default Text formatValue() { return Text.of(getString()); } + + default boolean isInputValid(String input) { + return true; + } + + @Override + default AbstractWidget provideWidget(YACLScreen screen, Dimension 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 index 0caaa93..3a07641 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java @@ -37,9 +37,4 @@ public class StringController implements IStringController { public void setFromString(String value) { option().requestSet(value); } - - @Override - public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { - return new StringControllerElement(this, screen, widgetDimension); - } } 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 index 0c3b7c9..bce6906 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java @@ -5,13 +5,17 @@ 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 org.lwjgl.glfw.GLFW; + +import java.util.function.Consumer; public class StringControllerElement extends ControllerWidget> { - protected StringBuilder inputField; + protected final boolean instantApply; + + protected String inputField; protected Dimension inputFieldBounds; protected boolean inputFieldFocused; @@ -22,12 +26,14 @@ public class StringControllerElement extends ControllerWidget control, YACLScreen screen, Dimension dim) { + public StringControllerElement(IStringController control, YACLScreen screen, Dimension dim, boolean instantApply) { super(control, screen, dim); - inputField = new StringBuilder(control.getString()); + 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); } @@ -35,12 +41,14 @@ public class StringControllerElement extends ControllerWidget { - inputFieldFocused = false; + case InputUtil.GLFW_KEY_ESCAPE, InputUtil.GLFW_KEY_ENTER -> { + unfocus(); return true; } - case GLFW.GLFW_KEY_LEFT -> { + case InputUtil.GLFW_KEY_LEFT -> { if (Screen.hasShiftDown()) { if (Screen.hasControlDown()) { int spaceChar = findSpaceIndex(true); @@ -98,14 +106,18 @@ public class StringControllerElement extends ControllerWidget 0) - caretPos--; + if (caretPos > 0) { + if (selectionLength != 0) + caretPos += Math.min(selectionLength, 0); + else + caretPos--; + } selectionLength = 0; } return true; } - case GLFW.GLFW_KEY_RIGHT -> { + case InputUtil.GLFW_KEY_RIGHT -> { if (Screen.hasShiftDown()) { if (Screen.hasControlDown()) { int spaceChar = findSpaceIndex(false); @@ -116,18 +128,22 @@ public class StringControllerElement extends ControllerWidget { + case InputUtil.GLFW_KEY_BACKSPACE -> { doBackspace(); return true; } - case GLFW.GLFW_KEY_DELETE -> { + case InputUtil.GLFW_KEY_DELETE -> { doDelete(); return true; } @@ -172,36 +188,46 @@ public class StringControllerElement extends ControllerWidget 0) { - inputField.deleteCharAt(caretPos - 1); - caretPos--; - updateControl(); + if (modifyInput(builder -> builder.deleteCharAt(caretPos - 1))) + caretPos--; } } protected void doDelete() { if (caretPos < inputField.length()) { - inputField.deleteCharAt(caretPos); - updateControl(); + modifyInput(builder -> builder.deleteCharAt(caretPos)); } } public void write(String string) { if (selectionLength == 0) { - string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString())); + String trimmed = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField)); - inputField.insert(caretPos, string); - caretPos += string.length(); + if (modifyInput(builder -> builder.insert(caretPos, trimmed))) { + caretPos += trimmed.length(); + } } else { int start = getSelectionStart(); int end = getSelectionEnd(); - string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString()) + textRenderer.getWidth(inputField.substring(start, end))); + String trimmed = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField) + textRenderer.getWidth(inputField.substring(start, end))); - inputField.replace(start, end, string); - caretPos = start + string.length(); - selectionLength = 0; + if (modifyInput(builder -> builder.replace(start, end, trimmed))) { + caretPos = start + trimmed.length(); + selectionLength = 0; + } } - updateControl(); + } + + public boolean modifyInput(Consumer 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() { @@ -249,6 +275,7 @@ public class StringControllerElement extends ControllerWidget { + 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 option, double min, double max, Function 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 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 option, Function 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 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 BigDecimal.valueOf(option().pendingValue()).stripTrailingZeros().toPlainString(); + } + + /** + * {@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..4b34d7f --- /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 { + 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 option, float min, float max, Function 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 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 option, Function 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 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 BigDecimal.valueOf(option().pendingValue()).stripTrailingZeros().toPlainString(); + } + + /** + * {@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..5f0121a --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java @@ -0,0 +1,104 @@ +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 { + 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 option, int min, int max, Function 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 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 option, Function 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 option) { + this(option, -Integer.MAX_VALUE, Integer.MAX_VALUE, IntegerSliderController.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((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..713d39f --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java @@ -0,0 +1,104 @@ +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 { + 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 option, long min, long max, Function 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 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 option, Function 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 option) { + this(option, -Long.MAX_VALUE, Long.MAX_VALUE, LongSliderController.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((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 number type + */ +public abstract class NumberFieldController implements ISliderController, IStringController { + private final Option option; + private final Function displayFormatter; + + public NumberFieldController(Option option, Function displayFormatter) { + this.option = option; + this.displayFormatter = displayFormatter; + } + + @Override + public Option 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 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 + *
    + *
  • For doubles: {@link dev.isxander.yacl.gui.controllers.string.number.DoubleFieldController}
  • + *
  • For floats: {@link dev.isxander.yacl.gui.controllers.string.number.FloatFieldController}
  • + *
  • For integers: {@link dev.isxander.yacl.gui.controllers.string.number.IntegerFieldController}
  • + *
  • For longs: {@link dev.isxander.yacl.gui.controllers.string.number.LongFieldController}
  • + *
+ */ +package dev.isxander.yacl.gui.controllers.string.number; diff --git a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java index 7377bc9..492a573 100644 --- a/src/testmod/java/dev/isxander/yacl/test/GuiTest.java +++ b/src/testmod/java/dev/isxander/yacl/test/GuiTest.java @@ -9,6 +9,10 @@ import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; import dev.isxander.yacl.gui.controllers.slider.LongSliderController; import dev.isxander.yacl.gui.controllers.string.StringController; +import dev.isxander.yacl.gui.controllers.string.number.DoubleFieldController; +import dev.isxander.yacl.gui.controllers.string.number.FloatFieldController; +import dev.isxander.yacl.gui.controllers.string.number.IntegerFieldController; +import dev.isxander.yacl.gui.controllers.string.number.LongFieldController; import dev.isxander.yacl.test.config.ConfigData; import dev.isxander.yacl.test.config.Entrypoint; import net.minecraft.client.MinecraftClient; @@ -157,6 +161,45 @@ public class GuiTest { .controller(ColorController::new) .build()) .build()) + .group(OptionGroup.createBuilder() + .name(Text.of("Number Fields")) + .option(Option.createBuilder(double.class) + .name(Text.of("Double Field")) + .binding( + defaults.doubleField, + () -> config.doubleField, + value -> config.doubleField = value + ) + .controller(DoubleFieldController::new) + .build()) + .option(Option.createBuilder(float.class) + .name(Text.of("Float Field")) + .binding( + defaults.floatField, + () -> config.floatField, + value -> config.floatField = value + ) + .controller(FloatFieldController::new) + .build()) + .option(Option.createBuilder(int.class) + .name(Text.of("Integer Field")) + .binding( + defaults.intField, + () -> config.intField, + value -> config.intField = value + ) + .controller(IntegerFieldController::new) + .build()) + .option(Option.createBuilder(long.class) + .name(Text.of("Long Field")) + .binding( + defaults.longField, + () -> config.longField, + value -> config.longField = value + ) + .controller(LongFieldController::new) + .build()) + .build()) .group(OptionGroup.createBuilder() .name(Text.of("Enum Controllers")) .option(Option.createBuilder(ConfigData.Alphabet.class) diff --git a/src/testmod/java/dev/isxander/yacl/test/config/ConfigData.java b/src/testmod/java/dev/isxander/yacl/test/config/ConfigData.java index 35e57dd..4eedd9f 100644 --- a/src/testmod/java/dev/isxander/yacl/test/config/ConfigData.java +++ b/src/testmod/java/dev/isxander/yacl/test/config/ConfigData.java @@ -14,6 +14,10 @@ public class ConfigData { @ConfigEntry public long longSlider = 0; @ConfigEntry public String textField = "Hello"; @ConfigEntry public Color colorOption = Color.red; + @ConfigEntry public double doubleField = 0.5; + @ConfigEntry public float floatField = 0.5f; + @ConfigEntry public int intField = 5; + @ConfigEntry public long longField = 5; @ConfigEntry public Alphabet enumOption = Alphabet.A; @ConfigEntry public boolean groupTestRoot = false; -- cgit