Inputting text when using an `IStringController` with input validation is overly restrictive
solonovamax opened this issue ยท 4 comments
When using an implementation of IStringController where input validation is used, the user is unable to enter an invalid value.
This is an issue for certain kinds of input validation, for example:
- validating if the input is a valid element of a registry
- validating if the input can be properly parsed
- etc.
Here is a concrete example:
class ModifierExpressionController(
private val option: Option<BeaconModifierExpression>,
) : IStringController<BeaconModifierExpression> {
override fun isInputValid(input: String): Boolean {
return try {
val expression = BeaconModifierExpression(input)
expression.evaluate(0.0) // try evaluating it to make sure it gets parsed (we parse it lazily)
true
} catch (e: ParseException) {
false
}
}
override fun option() = option
override fun getString() = option.pendingValue().expressionString
override fun setFromString(value: String) {
option.requestSet(BeaconModifierExpression(value))
}
}The input is expected to be an equation which can use various functions such as min, cos, log, etc., as well as standard math operations such as +, -, *, etc.
The issue is, if you wanted to input an expression such as 1 + 1. you will begin typing the expression by doing 1. This is a valid expression. But then you'll attempt to input + resulting in 1+. This is not a valid expression. However, the invalid expression is an intermediary step on the way to a valid one, as next 1 will be typed, resulting in 1+1.
The same thing might happen if you're, for example, required to input a valid registry entry, such as a valid mob id.
the mob id m is not a valid mob id, however it is required when attempting to type out the full id of minecraft:creeper.
I think a better alternative rather than not allowing the character to be typed would be to simply make the box red and make it the config unable to be saved, indicating that the input is invalid.
isValidInput is intended to prevent typing invalid characters completely.
You should do this sort of validation in setFromString where the user finalises their expression (clicks away).
isValidInputis intended to prevent typing invalid characters completely.You should do this sort of validation in
setFromStringwhere the user finalises their expression (clicks away).
ah, I see
is there any way to show that it has not been/will not be updated in the UI?
ie. making the outline of the box red.
isValidInputis intended to prevent typing invalid characters completely.You should do this sort of validation in
setFromStringwhere the user finalises their expression (clicks away).
This doesn't really work. While you can do validation, it can mess thing up pretty badly. Assume the only valid value is the starting value, "original".
First, I tried hitting backspace - the cursor moves, but the text remains. Once you hit backspace for every letter and finish editing, the box now says "Click to type...".
Clicking on it refills it with "original," however it behaves like there's no text there (can't click to move cursor, can't use arrow keys).
Typing 20 characters in moves the cursor again, but doesn't change the text. Hitting enter, the box says "original."
Selecting the box again and pressing Ctrl+A to select all, the game crashes.
Part of the stacktrace:
Caused by: java.lang.StringIndexOutOfBoundsException: Range [0, -8) out of bounds for length 8
at java.base/jdk.internal.util.Preconditions$1.apply(Preconditions.java:55) ~[?:?]
at java.base/jdk.internal.util.Preconditions$1.apply(Preconditions.java:52) ~[?:?]
at java.base/jdk.internal.util.Preconditions$4.apply(Preconditions.java:213) ~[?:?]
at java.base/jdk.internal.util.Preconditions$4.apply(Preconditions.java:210) ~[?:?]
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:98) ~[?:?]
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromToIndex(Preconditions.java:112) ~[?:?]
at java.base/jdk.internal.util.Preconditions.checkFromToIndex(Preconditions.java:349) ~[?:?]
at java.base/java.lang.String.checkBoundsBeginEnd(String.java:4865) ~[?:?]
at java.base/java.lang.String.substring(String.java:2834) ~[?:?]
at knot/dev.isxander.yacl3.gui.controllers.string.StringControllerElement.drawValueText(StringControllerElement.java:82) ~[yet-another-config-lib-3.6.6+1.21.5-fabric.jar:?]
at knot/dev.isxander.yacl3.gui.controllers.ControllerWidget.render(ControllerWidget.java:50) ~[yet-another-config-lib-3.6.6+1.21.5-fabric.jar:?]
Here's my class:
public class ExpressionController implements IStringController<Pair<String, Expression>> {
protected Option<Pair<String, Expression>> option;
protected Function<String, Expression> expressionParser;
public ExpressionController(Option<Pair<String, Expression>> option, Function<String, Expression> expressionParser) {
this.option = option;
this.expressionParser = expressionParser;
}
@Override
public String getString() {
return option.pendingValue().getFirst();
}
@Override
public Option<Pair<String, Expression>> option() {
return option;
}
@Override
public void setFromString(String s) {
try {
option.requestSet(Pair.of(s, expressionParser.apply(s)));
} catch (RuntimeException e) {
option.forgetPendingValue(); // Behaves the same with or without this call.
}
}
}Is there another workaround for this?