Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Textfield improvements #81

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/java/com/cleanroommc/modularui/ModularUIConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public class ModularUIConfig {
@Config.Comment("Default tooltip position around the widget or its panel.")
public static RichTooltip.Pos tooltipPos = RichTooltip.Pos.VERTICAL;

@Config.Comment("If true, pressing ESC key in the text field will restore the last text instead of confirming current one.")
public static boolean escRestoreLastText = false;

@Config.Comment("If true, widget outlines and widget information will be drawn.")
public static boolean guiDebugMode = FMLLaunchHandler.isDeobfuscatedEnvironment();

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/cleanroommc/modularui/api/IMathValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,11 @@ public interface IMathValue {
boolean booleanValue();

String stringValue();

class EvaluateException extends RuntimeException {

public EvaluateException(String message) {
super(message);
}
}
}
117 changes: 98 additions & 19 deletions src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@
import com.cleanroommc.modularui.utils.math.functions.string.StringStartsWith;
import com.cleanroommc.modularui.utils.math.functions.trig.*;
import com.cleanroommc.modularui.utils.math.functions.utility.*;
import com.cleanroommc.modularui.widgets.textfield.BaseTextFieldWidget;

import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Constructor;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand All @@ -43,6 +42,7 @@
public class MathBuilder {

public static final Pattern DECIMAL_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?([eE]-?\\d+)?");
private static final Pattern BRACKETS = Pattern.compile("[()]");

public static final MathBuilder INSTANCE = new MathBuilder();

Expand All @@ -66,7 +66,7 @@ public class MathBuilder {
public MathBuilder() {
/* Some default values */
this.register(new Variable("PI", Math.PI));
this.register(new Variable("E", Math.E));
this.register(new Variable("NAPIER", Math.E));

/* Rounding functions */
this.functions.put("floor", Floor.class);
Expand Down Expand Up @@ -127,17 +127,22 @@ public void register(Variable variable) {
* Parse given math expression into a {@link IMathValue} which can be
* used to execute math.
*/
public IMathValue parse(String expression) throws Exception {
public IMathValue parse(String expression) throws ParseException {
if (!BRACKETS.matcher(expression).find()) {
// Absense of bracket implies it's simple decimal expression maybe with operators,
// so comma is supposed to be representing a thousand separator instead of an argument separator
expression = expression.replace(",", "");
}
return this.parseSymbols(this.breakdownChars(this.breakdown(expression)));
}

/**
* Breakdown an expression
*/
public String[] breakdown(String expression) throws Exception {
public String[] breakdown(String expression) throws ParseException {
/* If given string have illegal characters, then it can't be parsed */
if (this.strict && !expression.matches("^[\\w\\s_+-/*%^&|<>=!?:.,()\"'@~\\[\\]]+$")) {
throw new Exception("Given expression '" + expression + "' contains illegal characters!");
throw new ParseException("Given expression '" + expression + "' contains illegal characters!");
}

String[] chars = expression.split("(?!^)");
Expand All @@ -156,7 +161,7 @@ public String[] breakdown(String expression) throws Exception {
/* Amount of left and right brackets should be the same */
if (left != right) {
// TODO: auto pre- or append ( or )
throw new Exception("Given expression '" + expression + "' has more uneven amount of parenthesis, there are " + left + " open and " + right + " closed!");
throw new ParseException("Given expression '" + expression + "' has more uneven amount of parenthesis, there are " + left + " open and " + right + " closed!");
}

return chars;
Expand Down Expand Up @@ -284,7 +289,7 @@ private List<Object> trimSymbols(List<?> symbols) {
* However, beside parsing operations, it's also can return one or
* two item sized symbol lists.
*/
public IMathValue parseSymbols(List<?> symbols) throws Exception {
public IMathValue parseSymbols(List<?> symbols) throws ParseException {
IMathValue ternary = this.tryTernary(symbols);

if (ternary != null) {
Expand All @@ -306,6 +311,8 @@ public IMathValue parseSymbols(List<?> symbols) throws Exception {
if ((this.isVariable(first) || first.equals("-")) && second instanceof List<?> list) {
return this.createFunction((String) first, list);
}
// This can happen with e.g. [15, %] and we cannot process this any further
throw new ParseException(String.format("Couldn't parse symbols: '%s%s'", first, second));
}

/* Any other math expression */
Expand Down Expand Up @@ -385,7 +392,7 @@ protected int seekLastOperator(List<?> symbols, int offset) {
* and some elements from beginning till ?, in between ? and :, and also some
* remaining elements after :.
*/
protected IMathValue tryTernary(List<?> symbols) throws Exception {
protected IMathValue tryTernary(List<?> symbols) throws ParseException {
int question = -1;
int questions = 0;
int colon = -1;
Expand Down Expand Up @@ -434,7 +441,7 @@ protected IMathValue tryTernary(List<?> symbols) throws Exception {
* mixed with operators, groups, values and commas. And then plug it
* in to a class constructor with given name.
*/
protected IMathValue createFunction(String first, List<?> args) throws Exception {
protected IMathValue createFunction(String first, List<?> args) throws ParseException {
/* Handle special cases with negation */
if (first.equals("!")) {
return new Negate(this.parseSymbols(args));
Expand All @@ -454,7 +461,7 @@ protected IMathValue createFunction(String first, List<?> args) throws Exception
}

if (!this.functions.containsKey(first)) {
throw new Exception("Function '" + first + "' couldn't be found!");
throw new ParseException("Function '" + first + "' couldn't be found!");
}

List<IMathValue> values = new ArrayList<>();
Expand All @@ -474,8 +481,17 @@ protected IMathValue createFunction(String first, List<?> args) throws Exception
}

Class<? extends Function> function = this.functions.get(first);
Constructor<? extends Function> ctor = function.getConstructor(IMathValue[].class, String.class);
return ctor.newInstance(values.toArray(new IMathValue[0]), first);
Constructor<? extends Function> ctor;
try {
ctor = function.getConstructor(IMathValue[].class, String.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
try {
return ctor.newInstance(values.toArray(new IMathValue[0]), first);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}

/**
Expand All @@ -486,7 +502,7 @@ protected IMathValue createFunction(String first, List<?> args) throws Exception
* groups.
*/
@SuppressWarnings("unchecked")
public IMathValue valueFromObject(Object object) throws Exception {
public IMathValue valueFromObject(Object object) throws ParseException {
if (object instanceof String symbol) {

/* Variable and constant negation */
Expand All @@ -498,8 +514,11 @@ public IMathValue valueFromObject(Object object) throws Exception {
return new Constant(symbol.substring(1, symbol.length() - 1));
}

if (this.isDecimal(symbol)) {
return new Constant(BaseTextFieldWidget.format.parse(symbol, new ParsePosition(0)).doubleValue());
symbol = trimThousandSeparator(symbol);
Double triedNumber = tryParseNumber(symbol);

if (triedNumber != null) {
return new Constant(triedNumber);
} else if (this.isVariable(symbol)) {
/* Need to account for a negative value variable */
if (symbol.startsWith("-")) {
Expand All @@ -522,27 +541,80 @@ public IMathValue valueFromObject(Object object) throws Exception {
return new Group(this.parseSymbols(list));
}

throw new Exception("Given object couldn't be converted to value! " + object);
throw new ParseException("Given object couldn't be converted to value! " + object);
}

protected String trimThousandSeparator(String symbol) {
return symbol.replace(" ", "")
.replace("\u202F", "") // French locale
.replace("_", "");
}

protected Double tryParseNumber(String symbol) throws ParseException {
final StringBuilder buffer = new StringBuilder();
final char[] characters = symbol.toCharArray();
Double leftNumber = null;
for (char c : characters) {
double multiplier = switch (c) {
case 'k', 'K' -> 1_000;
case 'm', 'M' -> 1_000_000;
case 'g', 'G', 'b', 'B' -> 1_000_000_000;
case 't', 'T' -> 1_000_000_000_000L;
case 's', 'S' -> 64;
case 'i', 'I' -> 144;
default -> 0;
};
if (multiplier == 0) {
// Not a suffix
if (leftNumber != null) {
// Something like 2k5 cannot be parsed
throw new ParseException(String.format("Symbol %s cannot be parsed!", symbol));
}
buffer.append(c);
} else {
if (leftNumber == null) {
// We haven't seen number so far, so try parse it
final String buffered = buffer.toString();
try {
leftNumber = Double.parseDouble(buffered);
} catch (NumberFormatException ignored) {
// Don't throw error here, as it could be variable name
return null;
}
}
// Continue parsing to allow 2kk == 2000k for example
leftNumber *= multiplier;
}
}
if (leftNumber != null) {
return leftNumber;
}
try {
return Double.parseDouble(symbol);
} catch (NumberFormatException ignored) {
throw new ParseException(String.format("Symbol %s cannot be parsed!", symbol));
}
}

/**
* Get variable
*/
@Nullable
protected Variable getVariable(String name) {
return this.variables.get(name);
}

/**
* Get operation for given operator strings
*/
protected Operation operationForOperator(String op) throws Exception {
protected Operation operationForOperator(String op) throws ParseException {
for (Operation operation : Operation.values()) {
if (operation.sign.equals(op)) {
return operation;
}
}

throw new Exception("There is no such operator '" + op + "'!");
throw new ParseException("There is no such operator '" + op + "'!");
}

/**
Expand Down Expand Up @@ -570,4 +642,11 @@ protected boolean isOperator(String s) {
protected boolean isDecimal(String s) {
return DECIMAL_PATTERN.matcher(s).matches();
}

public static class ParseException extends Exception {

public ParseException(String message) {
super(message);
}
}
}
20 changes: 18 additions & 2 deletions src/main/java/com/cleanroommc/modularui/utils/math/Operation.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.cleanroommc.modularui.utils.math;

import com.cleanroommc.modularui.api.IMathValue;

import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;

import java.util.Set;
Expand Down Expand Up @@ -36,8 +38,10 @@ public double calculate(double a, double b) {
DIV("/", 2) {
@Override
public double calculate(double a, double b) {
/* To avoid any exceptions */
return a / (b == 0 ? 1 : b);
if (b == 0) {
throw new IMathValue.EvaluateException(String.format("Division by zero: %s / %s", a, b));
}
return a / b;
}
},
MOD("%", 2) {
Expand All @@ -52,6 +56,18 @@ public double calculate(double a, double b) {
return Math.pow(a, b);
}
},
E_NOTATION_LOWERCASE("e", 4) {
@Override
public double calculate(double a, double b) {
return a * Math.pow(10, b);
}
},
E_NOTATION_UPPERCASE("E", 4) {
@Override
public double calculate(double a, double b) {
return a * Math.pow(10, b);
}
},
AND("&&", -3) {
@Override
public double calculate(double a, double b) {
Expand Down
Loading