From 690174e73a34368a5deb0d31a31cd69d505801bd Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 13:36:55 +0900 Subject: [PATCH 01/19] Allow focusing on text field upon GUI open --- .../widgets/textfield/BaseTextFieldWidget.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index 796fe7e1..1dedf38d 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -43,6 +43,7 @@ public class BaseTextFieldWidget> extends Scrol protected Alignment textAlignment = Alignment.CenterLeft; protected int scrollOffset = 0; protected float scale = 1f; + protected boolean focusOnGuiOpen; private int cursorTimer; protected boolean changedTextColor = false; @@ -73,6 +74,15 @@ public void onInit() { } } + @Override + public void afterInit() { + super.afterInit(); + if (this.focusOnGuiOpen) { + getContext().focus(this); + this.handler.markAll(); + } + } + @Override public void onUpdate() { super.onUpdate(); @@ -257,6 +267,11 @@ public W setTextColor(int color) { return getThis(); } + public W setFocusOnGuiOpen(boolean focusOnGuiOpen) { + this.focusOnGuiOpen = focusOnGuiOpen; + return getThis(); + } + public static char getDecimalSeparator() { return format.getDecimalFormatSymbols().getDecimalSeparator(); } From 59a67b670701e23c9a1dc33ff72802492ff84d16 Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 13:37:55 +0900 Subject: [PATCH 02/19] Remove selected chars on key type or paste Handler test prevents invalid chars from removing textbox --- .../modularui/widgets/textfield/BaseTextFieldWidget.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index 1dedf38d..226cd7f8 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -206,6 +206,9 @@ public void onMouseDrag(int mouseButton, long timeSinceClick) { GuiScreen.setClipboardString(this.handler.getSelectedText()); return Result.SUCCESS; } else if (GuiScreen.isKeyComboCtrlV(keyCode)) { + if (this.handler.hasTextMarked()) { + this.handler.delete(); + } // paste copied text in marked text this.handler.insert(GuiScreen.getClipboardString()); return Result.SUCCESS; @@ -218,7 +221,10 @@ public void onMouseDrag(int mouseButton, long timeSinceClick) { // mark whole text this.handler.markAll(); return Result.SUCCESS; - } else if (BASE_PATTERN.matcher(String.valueOf(character)).matches()) { + } else if (BASE_PATTERN.matcher(String.valueOf(character)).matches() && handler.test(String.valueOf(character))) { + if (this.handler.hasTextMarked()) { + this.handler.delete(); + } // insert typed char this.handler.insert(String.valueOf(character)); return Result.SUCCESS; From 3d2f69f1557a1f6bd964a713311d32607edc38e3 Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 14:04:22 +0900 Subject: [PATCH 03/19] =?UTF-8?q?Prevent=20=C2=A7=20from=20being=20pasted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modularui/widgets/textfield/BaseTextFieldWidget.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index 226cd7f8..56e3f467 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -210,7 +210,7 @@ public void onMouseDrag(int mouseButton, long timeSinceClick) { this.handler.delete(); } // paste copied text in marked text - this.handler.insert(GuiScreen.getClipboardString()); + this.handler.insert(GuiScreen.getClipboardString().replace("§", "")); return Result.SUCCESS; } else if (GuiScreen.isKeyComboCtrlX(keyCode) && this.handler.hasTextMarked()) { // copy and delete copied text From 5999aba46cce345375f3a2f55b8a2bf0c213fa0e Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 14:15:29 +0900 Subject: [PATCH 04/19] Shorten cursor blink rate to 0.5s This is the same behavior as of MUI1. Also Windows uses 530 ms by default. --- .../modularui/widgets/textfield/BaseTextFieldWidget.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index 56e3f467..a61b15ea 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -38,6 +38,8 @@ public class BaseTextFieldWidget> extends Scrol public static final Pattern ANY = Pattern.compile(".*"); private static final Pattern BASE_PATTERN = Pattern.compile("[^§]"); + private static final int CURSOR_BLINK_RATE = 10; + protected TextFieldHandler handler = new TextFieldHandler(this); protected TextFieldRenderer renderer = new TextFieldRenderer(this.handler); protected Alignment textAlignment = Alignment.CenterLeft; @@ -86,7 +88,7 @@ public void afterInit() { @Override public void onUpdate() { super.onUpdate(); - if (isFocused() && ++this.cursorTimer == 30) { + if (isFocused() && ++this.cursorTimer == CURSOR_BLINK_RATE) { this.renderer.toggleCursor(); this.cursorTimer = 0; } From c22e3a4007f8912dd3ebde90005ae746de057c6c Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 14:27:29 +0900 Subject: [PATCH 05/19] Parse thousand separator --- .../com/cleanroommc/modularui/utils/math/MathBuilder.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java index 8aa1ff78..d07d71c3 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java @@ -498,6 +498,8 @@ public IMathValue valueFromObject(Object object) throws Exception { return new Constant(symbol.substring(1, symbol.length() - 1)); } + symbol = trimThousandSeparator(symbol); + if (this.isDecimal(symbol)) { return new Constant(BaseTextFieldWidget.format.parse(symbol, new ParsePosition(0)).doubleValue()); } else if (this.isVariable(symbol)) { @@ -525,6 +527,12 @@ public IMathValue valueFromObject(Object object) throws Exception { throw new Exception("Given object couldn't be converted to value! " + object); } + protected String trimThousandSeparator(String symbol) { + return symbol.replace(" ", "") + .replace("\u202F", "") // French locale + .replace("_", ""); + } + /** * Get variable */ From 4f0a2464ca5434b3c8446a69b1f70ac3e4c2ee4e Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 16:11:26 +0900 Subject: [PATCH 06/19] Clear text on right click --- .../widgets/textfield/BaseTextFieldWidget.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index a61b15ea..6a797273 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -144,9 +144,14 @@ public void onRemoveFocus(ModularGuiContext context) { if (!isHovering()) { return Result.IGNORE; } - int x = getContext().getMouseX() + getScrollX(); - int y = getContext().getMouseY() + getScrollY(); - this.handler.setCursor(this.renderer.getCursorPos(this.handler.getText(), x, y), true); + if (mouseButton == 1) { + this.handler.markAll(); + this.handler.delete(); + } else { + int x = getContext().getMouseX() + getScrollX(); + int y = getContext().getMouseY() + getScrollY(); + this.handler.setCursor(this.renderer.getCursorPos(this.handler.getText(), x, y), true); + } return Result.SUCCESS; } From 461d3e8b73089ef327858d98b02b00ed23cd6372 Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 17:13:16 +0900 Subject: [PATCH 07/19] Add support for suffixes and E notation The implementation is somewhat dirty, but I can't think of better way. I renamed constant E to NAPIER, as it clashes with E notation. I don't think someone will use this anyway. --- .../modularui/utils/math/MathBuilder.java | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java index d07d71c3..0185188c 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java @@ -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; @@ -66,7 +65,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); @@ -499,9 +498,10 @@ public IMathValue valueFromObject(Object object) throws Exception { } symbol = trimThousandSeparator(symbol); + Double triedNumber = tryParseNumber(symbol); - if (this.isDecimal(symbol)) { - return new Constant(BaseTextFieldWidget.format.parse(symbol, new ParsePosition(0)).doubleValue()); + if (triedNumber != null) { + return new Constant(triedNumber); } else if (this.isVariable(symbol)) { /* Need to account for a negative value variable */ if (symbol.startsWith("-")) { @@ -533,9 +533,70 @@ protected String trimThousandSeparator(String symbol) { .replace("_", ""); } + protected Double tryParseNumber(String symbol) throws Exception { + final StringBuilder buffer = new StringBuilder(); + final char[] characters = symbol.toCharArray(); + Double leftNumber = null; + for (int i = 0; i < characters.length; i++) { + char c = characters[i]; + 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; + }; + final boolean isENotation = c == 'e' || c == 'E'; + if (multiplier == 0 && !isENotation) { + // Not a suffix nor E notation + if (leftNumber != null) { + // Something like 2k5 cannot be parsed + throw new Exception(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; + } + } + if (isENotation) { + final String rightString = symbol.substring(i + 1); + final double rightNumber; + try { + rightNumber = Double.parseDouble(rightString); + } catch (NumberFormatException ignored) { + // Left number is guaranteed to be nonnull here, and we don't have variable like 4ER + throw new Exception(String.format("Symbol %s cannot be parsed!", symbol)); + } + return leftNumber * Math.pow(10, rightNumber); + } else { + // 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 Exception(String.format("Symbol %s cannot be parsed!", symbol)); + } + } + /** * Get variable */ + @Nullable protected Variable getVariable(String name) { return this.variables.get(name); } From 0f9f81d16bc078565ce4cfe709e77b62d90cff36 Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 17:48:51 +0900 Subject: [PATCH 08/19] Allow retrieving parse fail message Printing to log might make people think it's bug in code. This feature can be used for indicator of what's wrong with the use input. This also stops function construction from failing silently. --- .../modularui/utils/math/MathBuilder.java | 52 ++++++++++++------- .../widgets/textfield/TextFieldWidget.java | 17 +++--- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java index 0185188c..11691edc 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java @@ -126,17 +126,17 @@ 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 { 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("(?!^)"); @@ -155,7 +155,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; @@ -283,7 +283,7 @@ private List 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) { @@ -384,7 +384,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; @@ -433,7 +433,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)); @@ -453,7 +453,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 values = new ArrayList<>(); @@ -473,8 +473,17 @@ protected IMathValue createFunction(String first, List args) throws Exception } Class function = this.functions.get(first); - Constructor ctor = function.getConstructor(IMathValue[].class, String.class); - return ctor.newInstance(values.toArray(new IMathValue[0]), first); + Constructor 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); + } } /** @@ -485,7 +494,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 */ @@ -524,7 +533,7 @@ 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) { @@ -533,7 +542,7 @@ protected String trimThousandSeparator(String symbol) { .replace("_", ""); } - protected Double tryParseNumber(String symbol) throws Exception { + protected Double tryParseNumber(String symbol) throws ParseException { final StringBuilder buffer = new StringBuilder(); final char[] characters = symbol.toCharArray(); Double leftNumber = null; @@ -553,7 +562,7 @@ protected Double tryParseNumber(String symbol) throws Exception { // Not a suffix nor E notation if (leftNumber != null) { // Something like 2k5 cannot be parsed - throw new Exception(String.format("Symbol %s cannot be parsed!", symbol)); + throw new ParseException(String.format("Symbol %s cannot be parsed!", symbol)); } buffer.append(c); } else { @@ -574,7 +583,7 @@ protected Double tryParseNumber(String symbol) throws Exception { rightNumber = Double.parseDouble(rightString); } catch (NumberFormatException ignored) { // Left number is guaranteed to be nonnull here, and we don't have variable like 4ER - throw new Exception(String.format("Symbol %s cannot be parsed!", symbol)); + throw new ParseException(String.format("Symbol %s cannot be parsed!", symbol)); } return leftNumber * Math.pow(10, rightNumber); } else { @@ -589,7 +598,7 @@ protected Double tryParseNumber(String symbol) throws Exception { try { return Double.parseDouble(symbol); } catch (NumberFormatException ignored) { - throw new Exception(String.format("Symbol %s cannot be parsed!", symbol)); + throw new ParseException(String.format("Symbol %s cannot be parsed!", symbol)); } } @@ -604,14 +613,14 @@ protected Variable getVariable(String 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 + "'!"); } /** @@ -639,4 +648,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); + } + } } \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index c49ff2e5..97a798c0 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -1,6 +1,5 @@ package com.cleanroommc.modularui.widgets.textfield; -import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.IMathValue; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IKey; @@ -30,19 +29,25 @@ public class TextFieldWidget extends BaseTextFieldWidget { private IStringValue stringValue; private Function validator = val -> val; private boolean numbers = false; + private String mathFailMessage = null; protected boolean changedMarkedColor = false; - public static IMathValue parse(String num) { + public IMathValue parse(String num) { try { - return MathBuilder.INSTANCE.parse(num); - } catch (Exception e) { - ModularUI.LOGGER.error("Failed to parse {} in TextFieldWidget", num); - ModularUI.LOGGER.catching(e); + IMathValue ret = MathBuilder.INSTANCE.parse(num); + this.mathFailMessage = null; + return ret; + } catch (MathBuilder.ParseException e) { + this.mathFailMessage = e.getMessage(); } return new Constant(0); } + public IStringValue createMathFailMessageValue() { + return new StringValue.Dynamic(() -> this.mathFailMessage, val -> this.mathFailMessage = val); + } + @Override public void onInit() { super.onInit(); From c0d667d4acd2c968af222972da0cc18f4ebf22ea Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 18:46:06 +0900 Subject: [PATCH 09/19] Allow setting default number --- .../widgets/textfield/TextFieldWidget.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index 97a798c0..de285fe4 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -30,6 +30,7 @@ public class TextFieldWidget extends BaseTextFieldWidget { private Function validator = val -> val; private boolean numbers = false; private String mathFailMessage = null; + private double defaultNumber = 0; protected boolean changedMarkedColor = false; @@ -41,7 +42,7 @@ public IMathValue parse(String num) { } catch (MathBuilder.ParseException e) { this.mathFailMessage = e.getMessage(); } - return new Constant(0); + return new Constant(this.defaultNumber); } public IStringValue createMathFailMessageValue() { @@ -187,12 +188,11 @@ public TextFieldWidget setValidator(Function validator) { } public TextFieldWidget setNumbersLong(Function validator) { - //setPattern(WHOLE_NUMS); this.numbers = true; setValidator(val -> { long num; if (val.isEmpty()) { - num = 0; + num = (long) this.defaultNumber; } else { num = (long) parse(val).doubleValue(); } @@ -202,12 +202,11 @@ public TextFieldWidget setNumbersLong(Function validator) { } public TextFieldWidget setNumbers(Function validator) { - //setPattern(WHOLE_NUMS); this.numbers = true; return setValidator(val -> { int num; if (val.isEmpty()) { - num = 0; + num = (int) this.defaultNumber; } else { num = (int) parse(val).doubleValue(); } @@ -216,12 +215,11 @@ public TextFieldWidget setNumbers(Function validator) { } public TextFieldWidget setNumbersDouble(Function validator) { - //setPattern(DECIMALS); this.numbers = true; return setValidator(val -> { double num; if (val.isEmpty()) { - num = 0; + num = this.defaultNumber; } else { num = parse(val).doubleValue(); } @@ -245,6 +243,11 @@ public TextFieldWidget setNumbers() { return setNumbers(Integer.MIN_VALUE, Integer.MAX_VALUE); } + public TextFieldWidget setDefaultNumber(double defaultNumber) { + this.defaultNumber = defaultNumber; + return this; + } + public TextFieldWidget value(IStringValue stringValue) { this.stringValue = stringValue; setValue(stringValue); From 3fd1008236a293318a5dc73425028e060033dee3 Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 18:59:21 +0900 Subject: [PATCH 10/19] Make division by zero throw exception I believe this is better than silently dividing by 1. --- .../cleanroommc/modularui/api/IMathValue.java | 7 +++++++ .../modularui/utils/math/Operation.java | 8 +++++-- .../widgets/textfield/TextFieldWidget.java | 21 ++++++++++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/api/IMathValue.java b/src/main/java/com/cleanroommc/modularui/api/IMathValue.java index f2e7404e..d6d12bdd 100644 --- a/src/main/java/com/cleanroommc/modularui/api/IMathValue.java +++ b/src/main/java/com/cleanroommc/modularui/api/IMathValue.java @@ -26,4 +26,11 @@ public interface IMathValue { boolean booleanValue(); String stringValue(); + + class EvaluateException extends RuntimeException { + + public EvaluateException(String message) { + super(message); + } + } } \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/Operation.java b/src/main/java/com/cleanroommc/modularui/utils/math/Operation.java index 73e62ed7..4eb72ce9 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/Operation.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/Operation.java @@ -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; @@ -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) { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index de285fe4..48d85d28 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -194,7 +194,12 @@ public TextFieldWidget setNumbersLong(Function validator) { if (val.isEmpty()) { num = (long) this.defaultNumber; } else { - num = (long) parse(val).doubleValue(); + try { + num = (long) parse(val).doubleValue(); + } catch (IMathValue.EvaluateException e) { + this.mathFailMessage = e.getMessage(); + num = (long) this.defaultNumber; + } } return format.format(validator.apply(num)); }); @@ -208,7 +213,12 @@ public TextFieldWidget setNumbers(Function validator) { if (val.isEmpty()) { num = (int) this.defaultNumber; } else { - num = (int) parse(val).doubleValue(); + try { + num = (int) parse(val).doubleValue(); + } catch (IMathValue.EvaluateException e) { + this.mathFailMessage = e.getMessage(); + num = (int) this.defaultNumber; + } } return format.format(validator.apply(num)); }); @@ -221,7 +231,12 @@ public TextFieldWidget setNumbersDouble(Function validator) { if (val.isEmpty()) { num = this.defaultNumber; } else { - num = parse(val).doubleValue(); + try { + num = parse(val).doubleValue(); + } catch (IMathValue.EvaluateException e) { + this.mathFailMessage = e.getMessage(); + num = this.defaultNumber; + } } return format.format(validator.apply(num)); }); From ca057627d39cd3e5fdca61e950f282a90b704f72 Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 19:37:19 +0900 Subject: [PATCH 11/19] Add config to restore the last text if ESC key is pressed --- .../cleanroommc/modularui/ModularUIConfig.java | 3 +++ .../widgets/textfield/BaseTextFieldWidget.java | 15 +++++++++++++-- .../widgets/textfield/TextFieldHandler.java | 5 +++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java b/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java index 7833d57d..fba67e86 100644 --- a/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java +++ b/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java @@ -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(); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index 6a797273..a268ef88 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.widgets.textfield; +import com.cleanroommc.modularui.ModularUIConfig; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.widget.IFocusedWidget; import com.cleanroommc.modularui.api.widget.IWidget; @@ -18,6 +19,7 @@ import org.lwjgl.input.Keyboard; import java.text.DecimalFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; @@ -43,6 +45,7 @@ public class BaseTextFieldWidget> extends Scrol protected TextFieldHandler handler = new TextFieldHandler(this); protected TextFieldRenderer renderer = new TextFieldRenderer(this.handler); protected Alignment textAlignment = Alignment.CenterLeft; + protected List lastText; protected int scrollOffset = 0; protected float scale = 1f; protected boolean focusOnGuiOpen; @@ -125,6 +128,7 @@ public boolean isFocused() { public void onFocus(ModularGuiContext context) { this.cursorTimer = 0; this.renderer.setCursor(true); + this.lastText = new ArrayList<>(this.handler.getText()); } @Override @@ -145,8 +149,7 @@ public void onRemoveFocus(ModularGuiContext context) { return Result.IGNORE; } if (mouseButton == 1) { - this.handler.markAll(); - this.handler.delete(); + this.handler.clear(); } else { int x = getContext().getMouseX() + getScrollX(); int y = getContext().getMouseY() + getScrollY(); @@ -178,6 +181,10 @@ public void onMouseDrag(int mouseButton, long timeSinceClick) { } return Result.SUCCESS; case Keyboard.KEY_ESCAPE: + if (ModularUIConfig.escRestoreLastText) { + this.handler.clear(); + this.handler.insert(this.lastText); + } getContext().removeFocus(); return Result.SUCCESS; case Keyboard.KEY_LEFT: { @@ -247,6 +254,10 @@ public ScrollData getScrollData() { return getScrollArea().getScrollX(); } + public List getLastText() { + return lastText; + } + public W setTextAlignment(Alignment textAlignment) { this.textAlignment = textAlignment; return getThis(); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldHandler.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldHandler.java index eab15895..730fc0b3 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldHandler.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldHandler.java @@ -324,6 +324,11 @@ public void newLine() { setCursor(this.cursor.y + 1, 0, false); } + public void clear() { + markAll(); + delete(); + } + public void delete() { delete(false); } From 63ac3ce3621d1c3325ea2b874adbeee4cbb14c9d Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 23:22:22 +0900 Subject: [PATCH 12/19] Handle cases failed to parse function --- .../java/com/cleanroommc/modularui/utils/math/MathBuilder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java index 11691edc..71c040de 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java @@ -305,6 +305,8 @@ public IMathValue parseSymbols(List symbols) throws ParseException { 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 */ From 6d2e0a3ecb3c8eb9d6f17dff4ab1d2cdf1b1f7b0 Mon Sep 17 00:00:00 2001 From: miozune Date: Mon, 9 Sep 2024 23:23:03 +0900 Subject: [PATCH 13/19] Catch all the exceptions It's not game-breaking fatal error anyway. --- .../modularui/widgets/textfield/TextFieldWidget.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index 48d85d28..2ec56a90 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.widgets.textfield; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.IMathValue; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IKey; @@ -41,6 +42,8 @@ public IMathValue parse(String num) { return ret; } catch (MathBuilder.ParseException e) { this.mathFailMessage = e.getMessage(); + } catch (Exception e) { + ModularUI.LOGGER.catching(e); } return new Constant(this.defaultNumber); } From 580683bcc4af9419be69d6e975a8834c0ee81878 Mon Sep 17 00:00:00 2001 From: miozune Date: Tue, 10 Sep 2024 14:40:54 +0900 Subject: [PATCH 14/19] Split into text field and numeric field Former defaultValue is gone, because 1. It's set by onInit 2. It's better to restore the last value rather than something supposed to be default --- .../modularui/test/ItemEditorGui.java | 13 +- .../cleanroommc/modularui/test/TestTile.java | 21 +- .../modularui/widgets/ColorPickerDialog.java | 4 +- .../widgets/textfield/DoubleFieldWidget.java | 62 ++++++ .../widgets/textfield/IntFieldWidget.java | 62 ++++++ .../widgets/textfield/LongFieldWidget.java | 62 ++++++ .../widgets/textfield/NumericFieldWidget.java | 44 +++++ .../textfield/StringTextFieldWidget.java | 54 ++++++ .../widgets/textfield/TextFieldWidget.java | 179 ++++-------------- 9 files changed, 346 insertions(+), 155 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/DoubleFieldWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/IntFieldWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/LongFieldWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java diff --git a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java index 3a3a3f78..d71898a5 100644 --- a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java @@ -13,7 +13,8 @@ import com.cleanroommc.modularui.widgets.layout.Column; import com.cleanroommc.modularui.widgets.layout.Row; import com.cleanroommc.modularui.widgets.slot.ModularSlot; -import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.IntFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; @@ -71,22 +72,22 @@ public ModularPanel buildUI(GuiData data, PanelSyncManager syncManager) { .height(16) .margin(0, 4) .child(IKey.str("Meta: ").asWidget()) - .child(new TextFieldWidget() + .child(new IntFieldWidget() .size(50, 16) .value(new IntSyncValue(() -> getStack().getMetadata(), val -> { if (!syncManager.isClient()) getStack().setItemDamage(val); })) - .setNumbers(0, Short.MAX_VALUE - 1)) + .setRange(0, Short.MAX_VALUE - 1)) .child(IKey.str(" Amount: ").asWidget()) - .child(new TextFieldWidget() + .child(new IntFieldWidget() .size(30, 16) .value(new IntSyncValue(() -> getStack().getCount(), value -> { if (!syncManager.isClient()) getStack().setCount(value); })) - .setNumbers(1, 127))) - .child(new TextFieldWidget() + .setRange(1, 127))) + .child(new StringTextFieldWidget() .height(20) .widthRel(1f) .value(new StringSyncValue(() -> getStack().hasTagCompound() ? getStack().getTagCompound().toString() : "", val -> { diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index bdb37d87..aa4ddfac 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -28,7 +28,9 @@ import com.cleanroommc.modularui.widgets.layout.Row; import com.cleanroommc.modularui.widgets.slot.ModularSlot; import com.cleanroommc.modularui.widgets.slot.SlotGroup; -import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.IntFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.LongFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; @@ -52,6 +54,7 @@ public class TestTile extends TileEntity implements IGuiHolder, ITic private int val, val2 = 0; private String value = ""; private double doubleValue = 1; + private long longValue = 25; private final int duration = 80; private int progress = 0; private int cycleState = 0; @@ -155,14 +158,14 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager) }) //.flex(flex -> flex.left(3)) // ? .overlay(IKey.str("Button 2"))) - .child(new TextFieldWidget() + .child(new StringTextFieldWidget() .size(60, 20) .value(SyncHandlers.string(() -> this.value, val -> this.value = val)) .margin(0, 3)) - .child(new TextFieldWidget() - .size(60, 20) - .value(SyncHandlers.doubleNumber(() -> this.doubleValue, val -> this.doubleValue = val)) - .setNumbersDouble(Function.identity())) + .child(new LongFieldWidget() + .size(100, 20) + .value(SyncHandlers.longNumber(() -> this.longValue, val -> this.longValue = val)) + .setRange(-10L, 1234567890123456789L)) .child(IKey.str("Test string").asWidget().padding(2).debugName("test string"))) .child(new Column() .debugName("button and slots test 2") @@ -288,9 +291,9 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager) .debugName("bogo test config 2") .widthRel(1f).height(14) .childPadding(2) - .child(new TextFieldWidget() + .child(new IntFieldWidget() .value(new IntValue.Dynamic(() -> this.num, val -> this.num = val)) - .setNumbers(1, Short.MAX_VALUE) + .setRange(1, Short.MAX_VALUE) .setTextAlignment(Alignment.Center) .background(new Rectangle().setColor(0xFFb1b1b1)) .setTextColor(IKey.TEXT_COLOR) @@ -370,7 +373,7 @@ public ModularPanel openThirdWindow(PanelSyncManager syncManager, PanelSyncHandl public void buildDialog(Dialog dialog) { AtomicReference value = new AtomicReference<>(""); dialog.setDraggable(true); - dialog.child(new TextFieldWidget() + dialog.child(new StringTextFieldWidget() .flex(flex -> flex.size(100, 20).align(Alignment.Center)) .value(new StringValue.Dynamic(value::get, value::set))) .child(new ButtonWidget<>() diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java b/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java index 8d23e381..1b6bd97c 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java @@ -13,7 +13,7 @@ import com.cleanroommc.modularui.value.StringValue; import com.cleanroommc.modularui.widgets.layout.Column; import com.cleanroommc.modularui.widgets.layout.Row; -import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; import java.util.function.Consumer; @@ -67,7 +67,7 @@ public ColorPickerDialog(String name, Consumer resultConsumer, int star .overlay(IKey.str("HSV")))) .child(new Row().widthRel(1f).height(12).marginTop(4) .child(IKey.str("Hex: ").asWidget().heightRel(1f)) - .child(new TextFieldWidget() + .child(new StringTextFieldWidget() .height(12) .expanded() .setValidator(this::validateRawColor) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/DoubleFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/DoubleFieldWidget.java new file mode 100644 index 00000000..c079ad55 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/DoubleFieldWidget.java @@ -0,0 +1,62 @@ +package com.cleanroommc.modularui.widgets.textfield; + +import com.cleanroommc.modularui.api.value.IDoubleValue; +import com.cleanroommc.modularui.utils.MathUtils; +import com.cleanroommc.modularui.value.DoubleValue; +import com.cleanroommc.modularui.value.sync.SyncHandler; + +import java.util.function.DoubleSupplier; +import java.util.function.DoubleUnaryOperator; + +public class DoubleFieldWidget extends NumericFieldWidget { + + private IDoubleValue doubleValue; + private DoubleUnaryOperator validator = val -> val; + + @Override + protected void setupValueIfNull() { + if (this.doubleValue == null) { + this.doubleValue = new DoubleValue(0); + } + } + + @Override + protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof IDoubleValue val) { + this.doubleValue = val; + return true; + } + return false; + } + + @Override + protected Number getNumberFromValue() { + return this.doubleValue.getDoubleValue(); + } + + @Override + protected void parseDisplayText(String text) { + double parsedVal = parse(text); + double validatedVal = this.validator.applyAsDouble(parsedVal); + this.doubleValue.setDoubleValue(validatedVal); + } + + public DoubleFieldWidget value(IDoubleValue value) { + this.doubleValue = value; + setValue(value); + return this; + } + + public DoubleFieldWidget setValidator(DoubleUnaryOperator validator) { + this.validator = validator; + return this; + } + + public DoubleFieldWidget setRange(double min, double max) { + return setValidator(val -> MathUtils.clamp(val, min, max)); + } + + public DoubleFieldWidget setRange(DoubleSupplier min, DoubleSupplier max) { + return setValidator(val -> MathUtils.clamp(val, min.getAsDouble(), max.getAsDouble())); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/IntFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/IntFieldWidget.java new file mode 100644 index 00000000..f78d6f1b --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/IntFieldWidget.java @@ -0,0 +1,62 @@ +package com.cleanroommc.modularui.widgets.textfield; + +import com.cleanroommc.modularui.api.value.IIntValue; +import com.cleanroommc.modularui.utils.MathUtils; +import com.cleanroommc.modularui.value.IntValue; +import com.cleanroommc.modularui.value.sync.SyncHandler; + +import java.util.function.IntSupplier; +import java.util.function.IntUnaryOperator; + +public class IntFieldWidget extends NumericFieldWidget { + + private IIntValue intValue; + private IntUnaryOperator validator = val -> val; + + @Override + protected void setupValueIfNull() { + if (this.intValue == null) { + this.intValue = new IntValue(0); + } + } + + @Override + protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof IIntValue val) { + this.intValue = val; + return true; + } + return false; + } + + @Override + protected Number getNumberFromValue() { + return this.intValue.getIntValue(); + } + + @Override + protected void parseDisplayText(String text) { + double parsedVal = parse(text); + int validatedVal = this.validator.applyAsInt((int) parsedVal); + this.intValue.setIntValue(validatedVal); + } + + public IntFieldWidget value(IIntValue value) { + this.intValue = value; + setValue(value); + return this; + } + + public IntFieldWidget setValidator(IntUnaryOperator validator) { + this.validator = validator; + return this; + } + + public IntFieldWidget setRange(int min, int max) { + return setValidator(val -> MathUtils.clamp(val, min, max)); + } + + public IntFieldWidget setRange(IntSupplier min, IntSupplier max) { + return setValidator(val -> MathUtils.clamp(val, min.getAsInt(), max.getAsInt())); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/LongFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/LongFieldWidget.java new file mode 100644 index 00000000..da068fd9 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/LongFieldWidget.java @@ -0,0 +1,62 @@ +package com.cleanroommc.modularui.widgets.textfield; + +import com.cleanroommc.modularui.api.value.ILongValue; +import com.cleanroommc.modularui.utils.MathUtils; +import com.cleanroommc.modularui.value.LongValue; +import com.cleanroommc.modularui.value.sync.SyncHandler; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +public class LongFieldWidget extends NumericFieldWidget { + + private ILongValue longValue; + private LongUnaryOperator validator = val -> val; + + @Override + protected void setupValueIfNull() { + if (this.longValue == null) { + this.longValue = new LongValue(0); + } + } + + @Override + protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof ILongValue val) { + this.longValue = val; + return true; + } + return false; + } + + @Override + protected Number getNumberFromValue() { + return this.longValue.getLongValue(); + } + + @Override + protected void parseDisplayText(String text) { + double parsedVal = parse(text); + long validatedVal = this.validator.applyAsLong((long) parsedVal); + this.longValue.setLongValue(validatedVal); + } + + public LongFieldWidget value(ILongValue value) { + this.longValue = value; + setValue(value); + return this; + } + + public LongFieldWidget setValidator(LongUnaryOperator validator) { + this.validator = validator; + return this; + } + + public LongFieldWidget setRange(long min, long max) { + return setValidator(val -> MathUtils.clamp(val, min, max)); + } + + public LongFieldWidget setRange(LongSupplier min, LongSupplier max) { + return setValidator(val -> MathUtils.clamp(val, min.getAsLong(), max.getAsLong())); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java new file mode 100644 index 00000000..2ed32c30 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java @@ -0,0 +1,44 @@ +package com.cleanroommc.modularui.widgets.textfield; + +import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.api.IMathValue; +import com.cleanroommc.modularui.api.value.IStringValue; +import com.cleanroommc.modularui.utils.math.MathBuilder; +import com.cleanroommc.modularui.value.StringValue; + +import org.jetbrains.annotations.NotNull; + +import java.text.DecimalFormat; + +public abstract class NumericFieldWidget> extends TextFieldWidget { + + protected final DecimalFormat format = new DecimalFormat(); + private String mathFailMessage = null; + + public double parse(String num) { + try { + IMathValue mathValue = MathBuilder.INSTANCE.parse(num); + double ret = mathValue.doubleValue(); + this.mathFailMessage = null; + return ret; + } catch (MathBuilder.ParseException | IMathValue.EvaluateException e) { + this.mathFailMessage = e.getMessage(); + } catch (Exception e) { + this.mathFailMessage = "Internal crash"; + ModularUI.LOGGER.catching(e); + } + return getNumberFromValue().doubleValue(); + } + + public IStringValue createMathFailMessageValue() { + return new StringValue.Dynamic(() -> this.mathFailMessage, val -> this.mathFailMessage = val); + } + + @NotNull + @Override + protected final String getDisplayTextFromValue() { + return format.format(getNumberFromValue()); + } + + protected abstract Number getNumberFromValue(); +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java new file mode 100644 index 00000000..30cfbd59 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java @@ -0,0 +1,54 @@ +package com.cleanroommc.modularui.widgets.textfield; + +import com.cleanroommc.modularui.api.value.IStringValue; +import com.cleanroommc.modularui.value.StringValue; +import com.cleanroommc.modularui.value.sync.SyncHandler; + +import org.jetbrains.annotations.NotNull; + +import java.util.function.Function; + +public class StringTextFieldWidget extends TextFieldWidget { + + private IStringValue stringValue; + private Function validator = val -> val; + + @Override + protected void setupValueIfNull() { + if (this.stringValue == null) { + this.stringValue = new StringValue(""); + } + } + + @Override + protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof IStringValue val) { + this.stringValue = val; + return true; + } + return false; + } + + @NotNull + @Override + protected String getDisplayTextFromValue() { + return this.stringValue.getStringValue(); + } + + @Override + protected void parseDisplayText(String text) { + String validatedText = this.validator.apply(text); + this.stringValue.setStringValue(validatedText); + } + + public StringTextFieldWidget value(IStringValue value) { + this.stringValue = value; + setValue(value); + return this; + } + + public StringTextFieldWidget setValidator(Function validator) { + this.validator = validator; + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index 2ec56a90..3a91be45 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -1,64 +1,54 @@ package com.cleanroommc.modularui.widgets.textfield; -import com.cleanroommc.modularui.ModularUI; -import com.cleanroommc.modularui.api.IMathValue; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.api.value.IStringValue; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; import com.cleanroommc.modularui.theme.WidgetTheme; -import com.cleanroommc.modularui.utils.math.Constant; -import com.cleanroommc.modularui.utils.math.MathBuilder; -import com.cleanroommc.modularui.value.StringValue; import com.cleanroommc.modularui.value.sync.SyncHandler; import com.cleanroommc.modularui.value.sync.ValueSyncHandler; import org.jetbrains.annotations.NotNull; import java.awt.*; -import java.text.ParsePosition; -import java.util.function.Function; -import java.util.function.Supplier; import java.util.regex.Pattern; /** * Text input widget with one line only. Can be synced between client and server. Can handle text validation. + *

+ * Child classes are expected to have Value bound to it. It's capable of doing anything to displayed text + * while keeping value consistent. */ -public class TextFieldWidget extends BaseTextFieldWidget { - - private IStringValue stringValue; - private Function validator = val -> val; - private boolean numbers = false; - private String mathFailMessage = null; - private double defaultNumber = 0; +public abstract class TextFieldWidget< W extends TextFieldWidget> extends BaseTextFieldWidget { protected boolean changedMarkedColor = false; - public IMathValue parse(String num) { - try { - IMathValue ret = MathBuilder.INSTANCE.parse(num); - this.mathFailMessage = null; - return ret; - } catch (MathBuilder.ParseException e) { - this.mathFailMessage = e.getMessage(); - } catch (Exception e) { - ModularUI.LOGGER.catching(e); - } - return new Constant(this.defaultNumber); - } + /** + * Called on widget init. Set default value if it's not set by user, so that it can be called anytime. + */ + protected abstract void setupValueIfNull(); - public IStringValue createMathFailMessageValue() { - return new StringValue.Dynamic(() -> this.mathFailMessage, val -> this.mathFailMessage = val); - } + /** + * Called when initializing sync handlers. If supplied sync handler can satisfy the requirement, save it and return true. + */ + protected abstract boolean checkAndSetSyncHandler(SyncHandler syncHandler); + + /** + * Return text to display derived from value. + */ + @NotNull + protected abstract String getDisplayTextFromValue(); + + /** + * Parse supplied display text and store to the value. + */ + protected abstract void parseDisplayText(String text); @Override public void onInit() { super.onInit(); - if (this.stringValue == null) { - this.stringValue = new StringValue(""); - } - setText(this.stringValue.getStringValue()); + setupValueIfNull(); + setText(getDisplayTextFromValue()); if (!hasTooltip()) { tooltipBuilder(tooltip -> tooltip.addLine(IKey.str(getText()))); } @@ -76,12 +66,11 @@ public int getMarkedColor() { } @Override - public boolean isValidSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof IStringValue iStringValue && syncHandler instanceof ValueSyncHandler valueSyncHandler) { - this.stringValue = iStringValue; + public final boolean isValidSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof ValueSyncHandler valueSyncHandler && checkAndSetSyncHandler(syncHandler)) { valueSyncHandler.setChangeListener(() -> { markTooltipDirty(); - setText(this.stringValue.getValue().toString()); + setText(getDisplayTextFromValue()); }); return true; } @@ -92,7 +81,7 @@ public boolean isValidSyncHandler(SyncHandler syncHandler) { public void onUpdate() { super.onUpdate(); if (!isFocused()) { - String s = this.stringValue.getStringValue(); + String s = getDisplayTextFromValue(); if (!getText().equals(s)) { setText(s); } @@ -148,14 +137,15 @@ public void onFocus(ModularGuiContext context) { public void onRemoveFocus(ModularGuiContext context) { super.onRemoveFocus(context); if (this.handler.getText().isEmpty()) { - this.handler.getText().add(this.validator.apply("")); + parseDisplayText(""); + this.handler.getText().add(getDisplayTextFromValue()); } else if (this.handler.getText().size() == 1) { - this.handler.getText().set(0, this.validator.apply(this.handler.getText().get(0))); + parseDisplayText(this.handler.getText().get(0)); + this.handler.getText().set(0, getDisplayTextFromValue()); markTooltipDirty(); } else { throw new IllegalStateException("TextFieldWidget can only have one line!"); } - this.stringValue.setStringValue(this.numbers ? format.parse(getText(), new ParsePosition(0)).toString() : getText()); } @Override @@ -163,112 +153,25 @@ public boolean canHover() { return true; } - public TextFieldWidget setMaxLength(int maxLength) { + public W setMaxLength(int maxLength) { this.handler.setMaxCharacters(maxLength); - return this; + return getThis(); } - public TextFieldWidget setPattern(Pattern pattern) { + public W setPattern(Pattern pattern) { this.handler.setPattern(pattern); - return this; + return getThis(); } - public TextFieldWidget setTextColor(int textColor) { + public W setTextColor(int textColor) { this.renderer.setColor(textColor); this.changedTextColor = true; - return this; + return getThis(); } - public TextFieldWidget setMarkedColor(int color) { + public W setMarkedColor(int color) { this.renderer.setMarkedColor(color); this.changedMarkedColor = true; - return this; - } - - public TextFieldWidget setValidator(Function validator) { - this.validator = validator; - return this; - } - - public TextFieldWidget setNumbersLong(Function validator) { - this.numbers = true; - setValidator(val -> { - long num; - if (val.isEmpty()) { - num = (long) this.defaultNumber; - } else { - try { - num = (long) parse(val).doubleValue(); - } catch (IMathValue.EvaluateException e) { - this.mathFailMessage = e.getMessage(); - num = (long) this.defaultNumber; - } - } - return format.format(validator.apply(num)); - }); - return this; - } - - public TextFieldWidget setNumbers(Function validator) { - this.numbers = true; - return setValidator(val -> { - int num; - if (val.isEmpty()) { - num = (int) this.defaultNumber; - } else { - try { - num = (int) parse(val).doubleValue(); - } catch (IMathValue.EvaluateException e) { - this.mathFailMessage = e.getMessage(); - num = (int) this.defaultNumber; - } - } - return format.format(validator.apply(num)); - }); - } - - public TextFieldWidget setNumbersDouble(Function validator) { - this.numbers = true; - return setValidator(val -> { - double num; - if (val.isEmpty()) { - num = this.defaultNumber; - } else { - try { - num = parse(val).doubleValue(); - } catch (IMathValue.EvaluateException e) { - this.mathFailMessage = e.getMessage(); - num = this.defaultNumber; - } - } - return format.format(validator.apply(num)); - }); - } - - public TextFieldWidget setNumbers(Supplier min, Supplier max) { - return setNumbers(val -> Math.min(max.get(), Math.max(min.get(), val))); - } - - public TextFieldWidget setNumbersLong(Supplier min, Supplier max) { - return setNumbersLong(val -> Math.min(max.get(), Math.max(min.get(), val))); - } - - public TextFieldWidget setNumbers(int min, int max) { - return setNumbers(val -> Math.min(max, Math.max(min, val))); - } - - public TextFieldWidget setNumbers() { - return setNumbers(Integer.MIN_VALUE, Integer.MAX_VALUE); - } - - public TextFieldWidget setDefaultNumber(double defaultNumber) { - this.defaultNumber = defaultNumber; - return this; - } - - public TextFieldWidget value(IStringValue stringValue) { - this.stringValue = stringValue; - setValue(stringValue); - return this; + return getThis(); } } From 8e00147da9ecedff50430fba71ec94fec6f8b920 Mon Sep 17 00:00:00 2001 From: miozune Date: Tue, 10 Sep 2024 14:44:15 +0900 Subject: [PATCH 15/19] Rename TextFieldWidget -> OneLineTextField StringTextFieldWidget -> TextFieldWidget --- .../modularui/test/ItemEditorGui.java | 4 +- .../cleanroommc/modularui/test/TestTile.java | 7 +- .../modularui/widgets/ColorPickerDialog.java | 4 +- .../widgets/textfield/NumericFieldWidget.java | 2 +- .../widgets/textfield/OneLineTextField.java | 177 ++++++++++++++++++ .../textfield/StringTextFieldWidget.java | 54 ------ .../widgets/textfield/TextFieldWidget.java | 171 +++-------------- 7 files changed, 209 insertions(+), 210 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/OneLineTextField.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java diff --git a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java index d71898a5..58b0ba4c 100644 --- a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java @@ -14,7 +14,7 @@ import com.cleanroommc.modularui.widgets.layout.Row; import com.cleanroommc.modularui.widgets.slot.ModularSlot; import com.cleanroommc.modularui.widgets.textfield.IntFieldWidget; -import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; @@ -87,7 +87,7 @@ public ModularPanel buildUI(GuiData data, PanelSyncManager syncManager) { getStack().setCount(value); })) .setRange(1, 127))) - .child(new StringTextFieldWidget() + .child(new TextFieldWidget() .height(20) .widthRel(1f) .value(new StringSyncValue(() -> getStack().hasTagCompound() ? getStack().getTagCompound().toString() : "", val -> { diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index aa4ddfac..6a7acb7f 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -30,7 +30,7 @@ import com.cleanroommc.modularui.widgets.slot.SlotGroup; import com.cleanroommc.modularui.widgets.textfield.IntFieldWidget; import com.cleanroommc.modularui.widgets.textfield.LongFieldWidget; -import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; @@ -44,7 +44,6 @@ import org.jetbrains.annotations.NotNull; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; public class TestTile extends TileEntity implements IGuiHolder, ITickable { @@ -158,7 +157,7 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager) }) //.flex(flex -> flex.left(3)) // ? .overlay(IKey.str("Button 2"))) - .child(new StringTextFieldWidget() + .child(new TextFieldWidget() .size(60, 20) .value(SyncHandlers.string(() -> this.value, val -> this.value = val)) .margin(0, 3)) @@ -373,7 +372,7 @@ public ModularPanel openThirdWindow(PanelSyncManager syncManager, PanelSyncHandl public void buildDialog(Dialog dialog) { AtomicReference value = new AtomicReference<>(""); dialog.setDraggable(true); - dialog.child(new StringTextFieldWidget() + dialog.child(new TextFieldWidget() .flex(flex -> flex.size(100, 20).align(Alignment.Center)) .value(new StringValue.Dynamic(value::get, value::set))) .child(new ButtonWidget<>() diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java b/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java index 1b6bd97c..8d23e381 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java @@ -13,7 +13,7 @@ import com.cleanroommc.modularui.value.StringValue; import com.cleanroommc.modularui.widgets.layout.Column; import com.cleanroommc.modularui.widgets.layout.Row; -import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import java.util.function.Consumer; @@ -67,7 +67,7 @@ public ColorPickerDialog(String name, Consumer resultConsumer, int star .overlay(IKey.str("HSV")))) .child(new Row().widthRel(1f).height(12).marginTop(4) .child(IKey.str("Hex: ").asWidget().heightRel(1f)) - .child(new StringTextFieldWidget() + .child(new TextFieldWidget() .height(12) .expanded() .setValidator(this::validateRawColor) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java index 2ed32c30..37b6766e 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java @@ -10,7 +10,7 @@ import java.text.DecimalFormat; -public abstract class NumericFieldWidget> extends TextFieldWidget { +public abstract class NumericFieldWidget> extends OneLineTextField { protected final DecimalFormat format = new DecimalFormat(); private String mathFailMessage = null; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/OneLineTextField.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/OneLineTextField.java new file mode 100644 index 00000000..312394d5 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/OneLineTextField.java @@ -0,0 +1,177 @@ +package com.cleanroommc.modularui.widgets.textfield; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.value.sync.SyncHandler; +import com.cleanroommc.modularui.value.sync.ValueSyncHandler; + +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.regex.Pattern; + +/** + * Text input widget with one line only. Can be synced between client and server. Can handle text validation. + *

+ * Child classes are expected to have Value bound to it. It's capable of doing anything to displayed text + * while keeping value consistent. + */ +public abstract class OneLineTextField< W extends OneLineTextField> extends BaseTextFieldWidget { + + protected boolean changedMarkedColor = false; + + /** + * Called on widget init. Set default value if it's not set by user, so that it can be called anytime. + */ + protected abstract void setupValueIfNull(); + + /** + * Called when initializing sync handlers. If supplied sync handler can satisfy the requirement, save it and return true. + */ + protected abstract boolean checkAndSetSyncHandler(SyncHandler syncHandler); + + /** + * Return text to display derived from value. + */ + @NotNull + protected abstract String getDisplayTextFromValue(); + + /** + * Parse supplied display text and store to the value. + */ + protected abstract void parseDisplayText(String text); + + @Override + public void onInit() { + super.onInit(); + setupValueIfNull(); + setText(getDisplayTextFromValue()); + if (!hasTooltip()) { + tooltipBuilder(tooltip -> tooltip.addLine(IKey.str(getText()))); + } + if (!this.changedMarkedColor) { + this.renderer.setMarkedColor(getMarkedColor()); + } + } + + public int getMarkedColor() { + WidgetTheme theme = getWidgetTheme(getContext().getTheme()); + if (theme instanceof WidgetTextFieldTheme textFieldTheme) { + return textFieldTheme.getMarkedColor(); + } + return ITheme.getDefault().getTextFieldTheme().getMarkedColor(); + } + + @Override + public final boolean isValidSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof ValueSyncHandler valueSyncHandler && checkAndSetSyncHandler(syncHandler)) { + valueSyncHandler.setChangeListener(() -> { + markTooltipDirty(); + setText(getDisplayTextFromValue()); + }); + return true; + } + return false; + } + + @Override + public void onUpdate() { + super.onUpdate(); + if (!isFocused()) { + String s = getDisplayTextFromValue(); + if (!getText().equals(s)) { + setText(s); + } + } + } + + @Override + public void drawText(ModularGuiContext context) { + this.renderer.setSimulate(false); + this.renderer.setPos(getArea().getPadding().left, 0); + this.renderer.setScale(this.scale); + this.renderer.setAlignment(this.textAlignment, -1, getArea().height); + this.renderer.draw(this.handler.getText()); + getScrollData().setScrollSize(Math.max(0, (int) this.renderer.getLastWidth())); + } + + @Override + public void drawForeground(ModularGuiContext context) { + if (hasTooltip() && getScrollData().isScrollBarActive(getScrollArea()) && isHoveringFor(getTooltip().getShowUpTimer())) { + getTooltip().draw(getContext()); + } + } + + @NotNull + public String getText() { + if (this.handler.getText().isEmpty()) { + return ""; + } + if (this.handler.getText().size() > 1) { + throw new IllegalStateException("TextFieldWidget can only have one line!"); + } + return this.handler.getText().get(0); + } + + public void setText(@NotNull String text) { + if (this.handler.getText().isEmpty()) { + this.handler.getText().add(text); + } else { + this.handler.getText().set(0, text); + } + } + + @Override + public void onFocus(ModularGuiContext context) { + super.onFocus(context); + Point main = this.handler.getMainCursor(); + if (main.x == 0) { + this.handler.setCursor(main.y, getText().length(), true, true); + } + } + + @Override + public void onRemoveFocus(ModularGuiContext context) { + super.onRemoveFocus(context); + if (this.handler.getText().isEmpty()) { + parseDisplayText(""); + this.handler.getText().add(getDisplayTextFromValue()); + } else if (this.handler.getText().size() == 1) { + parseDisplayText(this.handler.getText().get(0)); + this.handler.getText().set(0, getDisplayTextFromValue()); + markTooltipDirty(); + } else { + throw new IllegalStateException("TextFieldWidget can only have one line!"); + } + } + + @Override + public boolean canHover() { + return true; + } + + public W setMaxLength(int maxLength) { + this.handler.setMaxCharacters(maxLength); + return getThis(); + } + + public W setPattern(Pattern pattern) { + this.handler.setPattern(pattern); + return getThis(); + } + + public W setTextColor(int textColor) { + this.renderer.setColor(textColor); + this.changedTextColor = true; + return getThis(); + } + + public W setMarkedColor(int color) { + this.renderer.setMarkedColor(color); + this.changedMarkedColor = true; + return getThis(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java deleted file mode 100644 index 30cfbd59..00000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.cleanroommc.modularui.widgets.textfield; - -import com.cleanroommc.modularui.api.value.IStringValue; -import com.cleanroommc.modularui.value.StringValue; -import com.cleanroommc.modularui.value.sync.SyncHandler; - -import org.jetbrains.annotations.NotNull; - -import java.util.function.Function; - -public class StringTextFieldWidget extends TextFieldWidget { - - private IStringValue stringValue; - private Function validator = val -> val; - - @Override - protected void setupValueIfNull() { - if (this.stringValue == null) { - this.stringValue = new StringValue(""); - } - } - - @Override - protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof IStringValue val) { - this.stringValue = val; - return true; - } - return false; - } - - @NotNull - @Override - protected String getDisplayTextFromValue() { - return this.stringValue.getStringValue(); - } - - @Override - protected void parseDisplayText(String text) { - String validatedText = this.validator.apply(text); - this.stringValue.setStringValue(validatedText); - } - - public StringTextFieldWidget value(IStringValue value) { - this.stringValue = value; - setValue(value); - return this; - } - - public StringTextFieldWidget setValidator(Function validator) { - this.validator = validator; - return this; - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index 3a91be45..b5b85a22 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -1,177 +1,54 @@ package com.cleanroommc.modularui.widgets.textfield; -import com.cleanroommc.modularui.api.ITheme; -import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; -import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; -import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.api.value.IStringValue; +import com.cleanroommc.modularui.value.StringValue; import com.cleanroommc.modularui.value.sync.SyncHandler; -import com.cleanroommc.modularui.value.sync.ValueSyncHandler; import org.jetbrains.annotations.NotNull; -import java.awt.*; -import java.util.regex.Pattern; +import java.util.function.Function; -/** - * Text input widget with one line only. Can be synced between client and server. Can handle text validation. - *

- * Child classes are expected to have Value bound to it. It's capable of doing anything to displayed text - * while keeping value consistent. - */ -public abstract class TextFieldWidget< W extends TextFieldWidget> extends BaseTextFieldWidget { +public class TextFieldWidget extends OneLineTextField { - protected boolean changedMarkedColor = false; - - /** - * Called on widget init. Set default value if it's not set by user, so that it can be called anytime. - */ - protected abstract void setupValueIfNull(); - - /** - * Called when initializing sync handlers. If supplied sync handler can satisfy the requirement, save it and return true. - */ - protected abstract boolean checkAndSetSyncHandler(SyncHandler syncHandler); - - /** - * Return text to display derived from value. - */ - @NotNull - protected abstract String getDisplayTextFromValue(); - - /** - * Parse supplied display text and store to the value. - */ - protected abstract void parseDisplayText(String text); + private IStringValue stringValue; + private Function validator = val -> val; @Override - public void onInit() { - super.onInit(); - setupValueIfNull(); - setText(getDisplayTextFromValue()); - if (!hasTooltip()) { - tooltipBuilder(tooltip -> tooltip.addLine(IKey.str(getText()))); - } - if (!this.changedMarkedColor) { - this.renderer.setMarkedColor(getMarkedColor()); - } - } - - public int getMarkedColor() { - WidgetTheme theme = getWidgetTheme(getContext().getTheme()); - if (theme instanceof WidgetTextFieldTheme textFieldTheme) { - return textFieldTheme.getMarkedColor(); + protected void setupValueIfNull() { + if (this.stringValue == null) { + this.stringValue = new StringValue(""); } - return ITheme.getDefault().getTextFieldTheme().getMarkedColor(); } @Override - public final boolean isValidSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof ValueSyncHandler valueSyncHandler && checkAndSetSyncHandler(syncHandler)) { - valueSyncHandler.setChangeListener(() -> { - markTooltipDirty(); - setText(getDisplayTextFromValue()); - }); + protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof IStringValue val) { + this.stringValue = val; return true; } return false; } - @Override - public void onUpdate() { - super.onUpdate(); - if (!isFocused()) { - String s = getDisplayTextFromValue(); - if (!getText().equals(s)) { - setText(s); - } - } - } - - @Override - public void drawText(ModularGuiContext context) { - this.renderer.setSimulate(false); - this.renderer.setPos(getArea().getPadding().left, 0); - this.renderer.setScale(this.scale); - this.renderer.setAlignment(this.textAlignment, -1, getArea().height); - this.renderer.draw(this.handler.getText()); - getScrollData().setScrollSize(Math.max(0, (int) this.renderer.getLastWidth())); - } - - @Override - public void drawForeground(ModularGuiContext context) { - if (hasTooltip() && getScrollData().isScrollBarActive(getScrollArea()) && isHoveringFor(getTooltip().getShowUpTimer())) { - getTooltip().draw(getContext()); - } - } - @NotNull - public String getText() { - if (this.handler.getText().isEmpty()) { - return ""; - } - if (this.handler.getText().size() > 1) { - throw new IllegalStateException("TextFieldWidget can only have one line!"); - } - return this.handler.getText().get(0); - } - - public void setText(@NotNull String text) { - if (this.handler.getText().isEmpty()) { - this.handler.getText().add(text); - } else { - this.handler.getText().set(0, text); - } - } - @Override - public void onFocus(ModularGuiContext context) { - super.onFocus(context); - Point main = this.handler.getMainCursor(); - if (main.x == 0) { - this.handler.setCursor(main.y, getText().length(), true, true); - } - } - - @Override - public void onRemoveFocus(ModularGuiContext context) { - super.onRemoveFocus(context); - if (this.handler.getText().isEmpty()) { - parseDisplayText(""); - this.handler.getText().add(getDisplayTextFromValue()); - } else if (this.handler.getText().size() == 1) { - parseDisplayText(this.handler.getText().get(0)); - this.handler.getText().set(0, getDisplayTextFromValue()); - markTooltipDirty(); - } else { - throw new IllegalStateException("TextFieldWidget can only have one line!"); - } + protected String getDisplayTextFromValue() { + return this.stringValue.getStringValue(); } @Override - public boolean canHover() { - return true; - } - - public W setMaxLength(int maxLength) { - this.handler.setMaxCharacters(maxLength); - return getThis(); - } - - public W setPattern(Pattern pattern) { - this.handler.setPattern(pattern); - return getThis(); + protected void parseDisplayText(String text) { + String validatedText = this.validator.apply(text); + this.stringValue.setStringValue(validatedText); } - public W setTextColor(int textColor) { - this.renderer.setColor(textColor); - this.changedTextColor = true; - return getThis(); + public TextFieldWidget value(IStringValue value) { + this.stringValue = value; + setValue(value); + return this; } - public W setMarkedColor(int color) { - this.renderer.setMarkedColor(color); - this.changedMarkedColor = true; - return getThis(); + public TextFieldWidget setValidator(Function validator) { + this.validator = validator; + return this; } } From d22296474266af8b371b8bccbbf7ad205e8463c6 Mon Sep 17 00:00:00 2001 From: miozune Date: Tue, 10 Sep 2024 19:40:10 +0900 Subject: [PATCH 16/19] Dirty solution for comma as thousand separator --- .../com/cleanroommc/modularui/utils/math/MathBuilder.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java index 71c040de..08035c22 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java @@ -42,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(); @@ -127,6 +128,11 @@ public void register(Variable variable) { * used to execute math. */ 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))); } From 9eccea830680cb65c1651dd5c892ad5953d7a94e Mon Sep 17 00:00:00 2001 From: miozune Date: Tue, 10 Sep 2024 19:47:30 +0900 Subject: [PATCH 17/19] Rewrite the way E notation is handled --- .../modularui/utils/math/MathBuilder.java | 24 ++++--------------- .../modularui/utils/math/Operation.java | 12 ++++++++++ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java index 08035c22..90d47db0 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java @@ -554,8 +554,7 @@ protected Double tryParseNumber(String symbol) throws ParseException { final StringBuilder buffer = new StringBuilder(); final char[] characters = symbol.toCharArray(); Double leftNumber = null; - for (int i = 0; i < characters.length; i++) { - char c = characters[i]; + for (char c : characters) { double multiplier = switch (c) { case 'k', 'K' -> 1_000; case 'm', 'M' -> 1_000_000; @@ -565,9 +564,8 @@ protected Double tryParseNumber(String symbol) throws ParseException { case 'i', 'I' -> 144; default -> 0; }; - final boolean isENotation = c == 'e' || c == 'E'; - if (multiplier == 0 && !isENotation) { - // Not a suffix nor E notation + 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)); @@ -584,20 +582,8 @@ protected Double tryParseNumber(String symbol) throws ParseException { return null; } } - if (isENotation) { - final String rightString = symbol.substring(i + 1); - final double rightNumber; - try { - rightNumber = Double.parseDouble(rightString); - } catch (NumberFormatException ignored) { - // Left number is guaranteed to be nonnull here, and we don't have variable like 4ER - throw new ParseException(String.format("Symbol %s cannot be parsed!", symbol)); - } - return leftNumber * Math.pow(10, rightNumber); - } else { - // Continue parsing to allow 2kk == 2000k for example - leftNumber *= multiplier; - } + // Continue parsing to allow 2kk == 2000k for example + leftNumber *= multiplier; } } if (leftNumber != null) { diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/Operation.java b/src/main/java/com/cleanroommc/modularui/utils/math/Operation.java index 4eb72ce9..bc2a8d52 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/Operation.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/Operation.java @@ -56,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) { From 021d5a43a51c446945d1babfc04cc9be9d5290a0 Mon Sep 17 00:00:00 2001 From: miozune Date: Wed, 11 Sep 2024 19:24:28 +0900 Subject: [PATCH 18/19] Revert "Rename" This reverts commit 8e00147da9ecedff50430fba71ec94fec6f8b920. --- .../modularui/test/ItemEditorGui.java | 4 +- .../cleanroommc/modularui/test/TestTile.java | 7 +- .../modularui/widgets/ColorPickerDialog.java | 4 +- .../widgets/textfield/NumericFieldWidget.java | 2 +- .../widgets/textfield/OneLineTextField.java | 177 ------------------ .../textfield/StringTextFieldWidget.java | 54 ++++++ .../widgets/textfield/TextFieldWidget.java | 171 ++++++++++++++--- 7 files changed, 210 insertions(+), 209 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/OneLineTextField.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java diff --git a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java index 58b0ba4c..d71898a5 100644 --- a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java @@ -14,7 +14,7 @@ import com.cleanroommc.modularui.widgets.layout.Row; import com.cleanroommc.modularui.widgets.slot.ModularSlot; import com.cleanroommc.modularui.widgets.textfield.IntFieldWidget; -import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; @@ -87,7 +87,7 @@ public ModularPanel buildUI(GuiData data, PanelSyncManager syncManager) { getStack().setCount(value); })) .setRange(1, 127))) - .child(new TextFieldWidget() + .child(new StringTextFieldWidget() .height(20) .widthRel(1f) .value(new StringSyncValue(() -> getStack().hasTagCompound() ? getStack().getTagCompound().toString() : "", val -> { diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index 6a7acb7f..aa4ddfac 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -30,7 +30,7 @@ import com.cleanroommc.modularui.widgets.slot.SlotGroup; import com.cleanroommc.modularui.widgets.textfield.IntFieldWidget; import com.cleanroommc.modularui.widgets.textfield.LongFieldWidget; -import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; @@ -44,6 +44,7 @@ import org.jetbrains.annotations.NotNull; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; public class TestTile extends TileEntity implements IGuiHolder, ITickable { @@ -157,7 +158,7 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager) }) //.flex(flex -> flex.left(3)) // ? .overlay(IKey.str("Button 2"))) - .child(new TextFieldWidget() + .child(new StringTextFieldWidget() .size(60, 20) .value(SyncHandlers.string(() -> this.value, val -> this.value = val)) .margin(0, 3)) @@ -372,7 +373,7 @@ public ModularPanel openThirdWindow(PanelSyncManager syncManager, PanelSyncHandl public void buildDialog(Dialog dialog) { AtomicReference value = new AtomicReference<>(""); dialog.setDraggable(true); - dialog.child(new TextFieldWidget() + dialog.child(new StringTextFieldWidget() .flex(flex -> flex.size(100, 20).align(Alignment.Center)) .value(new StringValue.Dynamic(value::get, value::set))) .child(new ButtonWidget<>() diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java b/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java index 8d23e381..1b6bd97c 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java @@ -13,7 +13,7 @@ import com.cleanroommc.modularui.value.StringValue; import com.cleanroommc.modularui.widgets.layout.Column; import com.cleanroommc.modularui.widgets.layout.Row; -import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; import java.util.function.Consumer; @@ -67,7 +67,7 @@ public ColorPickerDialog(String name, Consumer resultConsumer, int star .overlay(IKey.str("HSV")))) .child(new Row().widthRel(1f).height(12).marginTop(4) .child(IKey.str("Hex: ").asWidget().heightRel(1f)) - .child(new TextFieldWidget() + .child(new StringTextFieldWidget() .height(12) .expanded() .setValidator(this::validateRawColor) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java index 37b6766e..2ed32c30 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java @@ -10,7 +10,7 @@ import java.text.DecimalFormat; -public abstract class NumericFieldWidget> extends OneLineTextField { +public abstract class NumericFieldWidget> extends TextFieldWidget { protected final DecimalFormat format = new DecimalFormat(); private String mathFailMessage = null; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/OneLineTextField.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/OneLineTextField.java deleted file mode 100644 index 312394d5..00000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/OneLineTextField.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.cleanroommc.modularui.widgets.textfield; - -import com.cleanroommc.modularui.api.ITheme; -import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; -import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; -import com.cleanroommc.modularui.theme.WidgetTheme; -import com.cleanroommc.modularui.value.sync.SyncHandler; -import com.cleanroommc.modularui.value.sync.ValueSyncHandler; - -import org.jetbrains.annotations.NotNull; - -import java.awt.*; -import java.util.regex.Pattern; - -/** - * Text input widget with one line only. Can be synced between client and server. Can handle text validation. - *

- * Child classes are expected to have Value bound to it. It's capable of doing anything to displayed text - * while keeping value consistent. - */ -public abstract class OneLineTextField< W extends OneLineTextField> extends BaseTextFieldWidget { - - protected boolean changedMarkedColor = false; - - /** - * Called on widget init. Set default value if it's not set by user, so that it can be called anytime. - */ - protected abstract void setupValueIfNull(); - - /** - * Called when initializing sync handlers. If supplied sync handler can satisfy the requirement, save it and return true. - */ - protected abstract boolean checkAndSetSyncHandler(SyncHandler syncHandler); - - /** - * Return text to display derived from value. - */ - @NotNull - protected abstract String getDisplayTextFromValue(); - - /** - * Parse supplied display text and store to the value. - */ - protected abstract void parseDisplayText(String text); - - @Override - public void onInit() { - super.onInit(); - setupValueIfNull(); - setText(getDisplayTextFromValue()); - if (!hasTooltip()) { - tooltipBuilder(tooltip -> tooltip.addLine(IKey.str(getText()))); - } - if (!this.changedMarkedColor) { - this.renderer.setMarkedColor(getMarkedColor()); - } - } - - public int getMarkedColor() { - WidgetTheme theme = getWidgetTheme(getContext().getTheme()); - if (theme instanceof WidgetTextFieldTheme textFieldTheme) { - return textFieldTheme.getMarkedColor(); - } - return ITheme.getDefault().getTextFieldTheme().getMarkedColor(); - } - - @Override - public final boolean isValidSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof ValueSyncHandler valueSyncHandler && checkAndSetSyncHandler(syncHandler)) { - valueSyncHandler.setChangeListener(() -> { - markTooltipDirty(); - setText(getDisplayTextFromValue()); - }); - return true; - } - return false; - } - - @Override - public void onUpdate() { - super.onUpdate(); - if (!isFocused()) { - String s = getDisplayTextFromValue(); - if (!getText().equals(s)) { - setText(s); - } - } - } - - @Override - public void drawText(ModularGuiContext context) { - this.renderer.setSimulate(false); - this.renderer.setPos(getArea().getPadding().left, 0); - this.renderer.setScale(this.scale); - this.renderer.setAlignment(this.textAlignment, -1, getArea().height); - this.renderer.draw(this.handler.getText()); - getScrollData().setScrollSize(Math.max(0, (int) this.renderer.getLastWidth())); - } - - @Override - public void drawForeground(ModularGuiContext context) { - if (hasTooltip() && getScrollData().isScrollBarActive(getScrollArea()) && isHoveringFor(getTooltip().getShowUpTimer())) { - getTooltip().draw(getContext()); - } - } - - @NotNull - public String getText() { - if (this.handler.getText().isEmpty()) { - return ""; - } - if (this.handler.getText().size() > 1) { - throw new IllegalStateException("TextFieldWidget can only have one line!"); - } - return this.handler.getText().get(0); - } - - public void setText(@NotNull String text) { - if (this.handler.getText().isEmpty()) { - this.handler.getText().add(text); - } else { - this.handler.getText().set(0, text); - } - } - - @Override - public void onFocus(ModularGuiContext context) { - super.onFocus(context); - Point main = this.handler.getMainCursor(); - if (main.x == 0) { - this.handler.setCursor(main.y, getText().length(), true, true); - } - } - - @Override - public void onRemoveFocus(ModularGuiContext context) { - super.onRemoveFocus(context); - if (this.handler.getText().isEmpty()) { - parseDisplayText(""); - this.handler.getText().add(getDisplayTextFromValue()); - } else if (this.handler.getText().size() == 1) { - parseDisplayText(this.handler.getText().get(0)); - this.handler.getText().set(0, getDisplayTextFromValue()); - markTooltipDirty(); - } else { - throw new IllegalStateException("TextFieldWidget can only have one line!"); - } - } - - @Override - public boolean canHover() { - return true; - } - - public W setMaxLength(int maxLength) { - this.handler.setMaxCharacters(maxLength); - return getThis(); - } - - public W setPattern(Pattern pattern) { - this.handler.setPattern(pattern); - return getThis(); - } - - public W setTextColor(int textColor) { - this.renderer.setColor(textColor); - this.changedTextColor = true; - return getThis(); - } - - public W setMarkedColor(int color) { - this.renderer.setMarkedColor(color); - this.changedMarkedColor = true; - return getThis(); - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java new file mode 100644 index 00000000..30cfbd59 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java @@ -0,0 +1,54 @@ +package com.cleanroommc.modularui.widgets.textfield; + +import com.cleanroommc.modularui.api.value.IStringValue; +import com.cleanroommc.modularui.value.StringValue; +import com.cleanroommc.modularui.value.sync.SyncHandler; + +import org.jetbrains.annotations.NotNull; + +import java.util.function.Function; + +public class StringTextFieldWidget extends TextFieldWidget { + + private IStringValue stringValue; + private Function validator = val -> val; + + @Override + protected void setupValueIfNull() { + if (this.stringValue == null) { + this.stringValue = new StringValue(""); + } + } + + @Override + protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof IStringValue val) { + this.stringValue = val; + return true; + } + return false; + } + + @NotNull + @Override + protected String getDisplayTextFromValue() { + return this.stringValue.getStringValue(); + } + + @Override + protected void parseDisplayText(String text) { + String validatedText = this.validator.apply(text); + this.stringValue.setStringValue(validatedText); + } + + public StringTextFieldWidget value(IStringValue value) { + this.stringValue = value; + setValue(value); + return this; + } + + public StringTextFieldWidget setValidator(Function validator) { + this.validator = validator; + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index b5b85a22..3a91be45 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -1,54 +1,177 @@ package com.cleanroommc.modularui.widgets.textfield; -import com.cleanroommc.modularui.api.value.IStringValue; -import com.cleanroommc.modularui.value.StringValue; +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; +import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.value.sync.SyncHandler; +import com.cleanroommc.modularui.value.sync.ValueSyncHandler; import org.jetbrains.annotations.NotNull; -import java.util.function.Function; +import java.awt.*; +import java.util.regex.Pattern; -public class TextFieldWidget extends OneLineTextField { +/** + * Text input widget with one line only. Can be synced between client and server. Can handle text validation. + *

+ * Child classes are expected to have Value bound to it. It's capable of doing anything to displayed text + * while keeping value consistent. + */ +public abstract class TextFieldWidget< W extends TextFieldWidget> extends BaseTextFieldWidget { - private IStringValue stringValue; - private Function validator = val -> val; + protected boolean changedMarkedColor = false; + + /** + * Called on widget init. Set default value if it's not set by user, so that it can be called anytime. + */ + protected abstract void setupValueIfNull(); + + /** + * Called when initializing sync handlers. If supplied sync handler can satisfy the requirement, save it and return true. + */ + protected abstract boolean checkAndSetSyncHandler(SyncHandler syncHandler); + + /** + * Return text to display derived from value. + */ + @NotNull + protected abstract String getDisplayTextFromValue(); + + /** + * Parse supplied display text and store to the value. + */ + protected abstract void parseDisplayText(String text); @Override - protected void setupValueIfNull() { - if (this.stringValue == null) { - this.stringValue = new StringValue(""); + public void onInit() { + super.onInit(); + setupValueIfNull(); + setText(getDisplayTextFromValue()); + if (!hasTooltip()) { + tooltipBuilder(tooltip -> tooltip.addLine(IKey.str(getText()))); + } + if (!this.changedMarkedColor) { + this.renderer.setMarkedColor(getMarkedColor()); + } + } + + public int getMarkedColor() { + WidgetTheme theme = getWidgetTheme(getContext().getTheme()); + if (theme instanceof WidgetTextFieldTheme textFieldTheme) { + return textFieldTheme.getMarkedColor(); } + return ITheme.getDefault().getTextFieldTheme().getMarkedColor(); } @Override - protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof IStringValue val) { - this.stringValue = val; + public final boolean isValidSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof ValueSyncHandler valueSyncHandler && checkAndSetSyncHandler(syncHandler)) { + valueSyncHandler.setChangeListener(() -> { + markTooltipDirty(); + setText(getDisplayTextFromValue()); + }); return true; } return false; } + @Override + public void onUpdate() { + super.onUpdate(); + if (!isFocused()) { + String s = getDisplayTextFromValue(); + if (!getText().equals(s)) { + setText(s); + } + } + } + + @Override + public void drawText(ModularGuiContext context) { + this.renderer.setSimulate(false); + this.renderer.setPos(getArea().getPadding().left, 0); + this.renderer.setScale(this.scale); + this.renderer.setAlignment(this.textAlignment, -1, getArea().height); + this.renderer.draw(this.handler.getText()); + getScrollData().setScrollSize(Math.max(0, (int) this.renderer.getLastWidth())); + } + + @Override + public void drawForeground(ModularGuiContext context) { + if (hasTooltip() && getScrollData().isScrollBarActive(getScrollArea()) && isHoveringFor(getTooltip().getShowUpTimer())) { + getTooltip().draw(getContext()); + } + } + @NotNull + public String getText() { + if (this.handler.getText().isEmpty()) { + return ""; + } + if (this.handler.getText().size() > 1) { + throw new IllegalStateException("TextFieldWidget can only have one line!"); + } + return this.handler.getText().get(0); + } + + public void setText(@NotNull String text) { + if (this.handler.getText().isEmpty()) { + this.handler.getText().add(text); + } else { + this.handler.getText().set(0, text); + } + } + @Override - protected String getDisplayTextFromValue() { - return this.stringValue.getStringValue(); + public void onFocus(ModularGuiContext context) { + super.onFocus(context); + Point main = this.handler.getMainCursor(); + if (main.x == 0) { + this.handler.setCursor(main.y, getText().length(), true, true); + } + } + + @Override + public void onRemoveFocus(ModularGuiContext context) { + super.onRemoveFocus(context); + if (this.handler.getText().isEmpty()) { + parseDisplayText(""); + this.handler.getText().add(getDisplayTextFromValue()); + } else if (this.handler.getText().size() == 1) { + parseDisplayText(this.handler.getText().get(0)); + this.handler.getText().set(0, getDisplayTextFromValue()); + markTooltipDirty(); + } else { + throw new IllegalStateException("TextFieldWidget can only have one line!"); + } } @Override - protected void parseDisplayText(String text) { - String validatedText = this.validator.apply(text); - this.stringValue.setStringValue(validatedText); + public boolean canHover() { + return true; + } + + public W setMaxLength(int maxLength) { + this.handler.setMaxCharacters(maxLength); + return getThis(); + } + + public W setPattern(Pattern pattern) { + this.handler.setPattern(pattern); + return getThis(); } - public TextFieldWidget value(IStringValue value) { - this.stringValue = value; - setValue(value); - return this; + public W setTextColor(int textColor) { + this.renderer.setColor(textColor); + this.changedTextColor = true; + return getThis(); } - public TextFieldWidget setValidator(Function validator) { - this.validator = validator; - return this; + public W setMarkedColor(int color) { + this.renderer.setMarkedColor(color); + this.changedMarkedColor = true; + return getThis(); } } From fb0ad1c07c1b2a62dfb43525e56fb06bcb0eb5a9 Mon Sep 17 00:00:00 2001 From: miozune Date: Wed, 11 Sep 2024 19:24:47 +0900 Subject: [PATCH 19/19] Revert "Split into text field and numeric field" This reverts commit 580683bcc4af9419be69d6e975a8834c0ee81878. --- .../modularui/test/ItemEditorGui.java | 13 +- .../cleanroommc/modularui/test/TestTile.java | 21 +- .../modularui/widgets/ColorPickerDialog.java | 4 +- .../widgets/textfield/DoubleFieldWidget.java | 62 ------ .../widgets/textfield/IntFieldWidget.java | 62 ------ .../widgets/textfield/LongFieldWidget.java | 62 ------ .../widgets/textfield/NumericFieldWidget.java | 44 ----- .../textfield/StringTextFieldWidget.java | 54 ------ .../widgets/textfield/TextFieldWidget.java | 179 ++++++++++++++---- 9 files changed, 155 insertions(+), 346 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/DoubleFieldWidget.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/IntFieldWidget.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/LongFieldWidget.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java diff --git a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java index d71898a5..3a3a3f78 100644 --- a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java @@ -13,8 +13,7 @@ import com.cleanroommc.modularui.widgets.layout.Column; import com.cleanroommc.modularui.widgets.layout.Row; import com.cleanroommc.modularui.widgets.slot.ModularSlot; -import com.cleanroommc.modularui.widgets.textfield.IntFieldWidget; -import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; @@ -72,22 +71,22 @@ public ModularPanel buildUI(GuiData data, PanelSyncManager syncManager) { .height(16) .margin(0, 4) .child(IKey.str("Meta: ").asWidget()) - .child(new IntFieldWidget() + .child(new TextFieldWidget() .size(50, 16) .value(new IntSyncValue(() -> getStack().getMetadata(), val -> { if (!syncManager.isClient()) getStack().setItemDamage(val); })) - .setRange(0, Short.MAX_VALUE - 1)) + .setNumbers(0, Short.MAX_VALUE - 1)) .child(IKey.str(" Amount: ").asWidget()) - .child(new IntFieldWidget() + .child(new TextFieldWidget() .size(30, 16) .value(new IntSyncValue(() -> getStack().getCount(), value -> { if (!syncManager.isClient()) getStack().setCount(value); })) - .setRange(1, 127))) - .child(new StringTextFieldWidget() + .setNumbers(1, 127))) + .child(new TextFieldWidget() .height(20) .widthRel(1f) .value(new StringSyncValue(() -> getStack().hasTagCompound() ? getStack().getTagCompound().toString() : "", val -> { diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index aa4ddfac..bdb37d87 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -28,9 +28,7 @@ import com.cleanroommc.modularui.widgets.layout.Row; import com.cleanroommc.modularui.widgets.slot.ModularSlot; import com.cleanroommc.modularui.widgets.slot.SlotGroup; -import com.cleanroommc.modularui.widgets.textfield.IntFieldWidget; -import com.cleanroommc.modularui.widgets.textfield.LongFieldWidget; -import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; @@ -54,7 +52,6 @@ public class TestTile extends TileEntity implements IGuiHolder, ITic private int val, val2 = 0; private String value = ""; private double doubleValue = 1; - private long longValue = 25; private final int duration = 80; private int progress = 0; private int cycleState = 0; @@ -158,14 +155,14 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager) }) //.flex(flex -> flex.left(3)) // ? .overlay(IKey.str("Button 2"))) - .child(new StringTextFieldWidget() + .child(new TextFieldWidget() .size(60, 20) .value(SyncHandlers.string(() -> this.value, val -> this.value = val)) .margin(0, 3)) - .child(new LongFieldWidget() - .size(100, 20) - .value(SyncHandlers.longNumber(() -> this.longValue, val -> this.longValue = val)) - .setRange(-10L, 1234567890123456789L)) + .child(new TextFieldWidget() + .size(60, 20) + .value(SyncHandlers.doubleNumber(() -> this.doubleValue, val -> this.doubleValue = val)) + .setNumbersDouble(Function.identity())) .child(IKey.str("Test string").asWidget().padding(2).debugName("test string"))) .child(new Column() .debugName("button and slots test 2") @@ -291,9 +288,9 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager) .debugName("bogo test config 2") .widthRel(1f).height(14) .childPadding(2) - .child(new IntFieldWidget() + .child(new TextFieldWidget() .value(new IntValue.Dynamic(() -> this.num, val -> this.num = val)) - .setRange(1, Short.MAX_VALUE) + .setNumbers(1, Short.MAX_VALUE) .setTextAlignment(Alignment.Center) .background(new Rectangle().setColor(0xFFb1b1b1)) .setTextColor(IKey.TEXT_COLOR) @@ -373,7 +370,7 @@ public ModularPanel openThirdWindow(PanelSyncManager syncManager, PanelSyncHandl public void buildDialog(Dialog dialog) { AtomicReference value = new AtomicReference<>(""); dialog.setDraggable(true); - dialog.child(new StringTextFieldWidget() + dialog.child(new TextFieldWidget() .flex(flex -> flex.size(100, 20).align(Alignment.Center)) .value(new StringValue.Dynamic(value::get, value::set))) .child(new ButtonWidget<>() diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java b/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java index 1b6bd97c..8d23e381 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ColorPickerDialog.java @@ -13,7 +13,7 @@ import com.cleanroommc.modularui.value.StringValue; import com.cleanroommc.modularui.widgets.layout.Column; import com.cleanroommc.modularui.widgets.layout.Row; -import com.cleanroommc.modularui.widgets.textfield.StringTextFieldWidget; +import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import java.util.function.Consumer; @@ -67,7 +67,7 @@ public ColorPickerDialog(String name, Consumer resultConsumer, int star .overlay(IKey.str("HSV")))) .child(new Row().widthRel(1f).height(12).marginTop(4) .child(IKey.str("Hex: ").asWidget().heightRel(1f)) - .child(new StringTextFieldWidget() + .child(new TextFieldWidget() .height(12) .expanded() .setValidator(this::validateRawColor) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/DoubleFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/DoubleFieldWidget.java deleted file mode 100644 index c079ad55..00000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/DoubleFieldWidget.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.cleanroommc.modularui.widgets.textfield; - -import com.cleanroommc.modularui.api.value.IDoubleValue; -import com.cleanroommc.modularui.utils.MathUtils; -import com.cleanroommc.modularui.value.DoubleValue; -import com.cleanroommc.modularui.value.sync.SyncHandler; - -import java.util.function.DoubleSupplier; -import java.util.function.DoubleUnaryOperator; - -public class DoubleFieldWidget extends NumericFieldWidget { - - private IDoubleValue doubleValue; - private DoubleUnaryOperator validator = val -> val; - - @Override - protected void setupValueIfNull() { - if (this.doubleValue == null) { - this.doubleValue = new DoubleValue(0); - } - } - - @Override - protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof IDoubleValue val) { - this.doubleValue = val; - return true; - } - return false; - } - - @Override - protected Number getNumberFromValue() { - return this.doubleValue.getDoubleValue(); - } - - @Override - protected void parseDisplayText(String text) { - double parsedVal = parse(text); - double validatedVal = this.validator.applyAsDouble(parsedVal); - this.doubleValue.setDoubleValue(validatedVal); - } - - public DoubleFieldWidget value(IDoubleValue value) { - this.doubleValue = value; - setValue(value); - return this; - } - - public DoubleFieldWidget setValidator(DoubleUnaryOperator validator) { - this.validator = validator; - return this; - } - - public DoubleFieldWidget setRange(double min, double max) { - return setValidator(val -> MathUtils.clamp(val, min, max)); - } - - public DoubleFieldWidget setRange(DoubleSupplier min, DoubleSupplier max) { - return setValidator(val -> MathUtils.clamp(val, min.getAsDouble(), max.getAsDouble())); - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/IntFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/IntFieldWidget.java deleted file mode 100644 index f78d6f1b..00000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/IntFieldWidget.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.cleanroommc.modularui.widgets.textfield; - -import com.cleanroommc.modularui.api.value.IIntValue; -import com.cleanroommc.modularui.utils.MathUtils; -import com.cleanroommc.modularui.value.IntValue; -import com.cleanroommc.modularui.value.sync.SyncHandler; - -import java.util.function.IntSupplier; -import java.util.function.IntUnaryOperator; - -public class IntFieldWidget extends NumericFieldWidget { - - private IIntValue intValue; - private IntUnaryOperator validator = val -> val; - - @Override - protected void setupValueIfNull() { - if (this.intValue == null) { - this.intValue = new IntValue(0); - } - } - - @Override - protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof IIntValue val) { - this.intValue = val; - return true; - } - return false; - } - - @Override - protected Number getNumberFromValue() { - return this.intValue.getIntValue(); - } - - @Override - protected void parseDisplayText(String text) { - double parsedVal = parse(text); - int validatedVal = this.validator.applyAsInt((int) parsedVal); - this.intValue.setIntValue(validatedVal); - } - - public IntFieldWidget value(IIntValue value) { - this.intValue = value; - setValue(value); - return this; - } - - public IntFieldWidget setValidator(IntUnaryOperator validator) { - this.validator = validator; - return this; - } - - public IntFieldWidget setRange(int min, int max) { - return setValidator(val -> MathUtils.clamp(val, min, max)); - } - - public IntFieldWidget setRange(IntSupplier min, IntSupplier max) { - return setValidator(val -> MathUtils.clamp(val, min.getAsInt(), max.getAsInt())); - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/LongFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/LongFieldWidget.java deleted file mode 100644 index da068fd9..00000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/LongFieldWidget.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.cleanroommc.modularui.widgets.textfield; - -import com.cleanroommc.modularui.api.value.ILongValue; -import com.cleanroommc.modularui.utils.MathUtils; -import com.cleanroommc.modularui.value.LongValue; -import com.cleanroommc.modularui.value.sync.SyncHandler; - -import java.util.function.LongSupplier; -import java.util.function.LongUnaryOperator; - -public class LongFieldWidget extends NumericFieldWidget { - - private ILongValue longValue; - private LongUnaryOperator validator = val -> val; - - @Override - protected void setupValueIfNull() { - if (this.longValue == null) { - this.longValue = new LongValue(0); - } - } - - @Override - protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof ILongValue val) { - this.longValue = val; - return true; - } - return false; - } - - @Override - protected Number getNumberFromValue() { - return this.longValue.getLongValue(); - } - - @Override - protected void parseDisplayText(String text) { - double parsedVal = parse(text); - long validatedVal = this.validator.applyAsLong((long) parsedVal); - this.longValue.setLongValue(validatedVal); - } - - public LongFieldWidget value(ILongValue value) { - this.longValue = value; - setValue(value); - return this; - } - - public LongFieldWidget setValidator(LongUnaryOperator validator) { - this.validator = validator; - return this; - } - - public LongFieldWidget setRange(long min, long max) { - return setValidator(val -> MathUtils.clamp(val, min, max)); - } - - public LongFieldWidget setRange(LongSupplier min, LongSupplier max) { - return setValidator(val -> MathUtils.clamp(val, min.getAsLong(), max.getAsLong())); - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java deleted file mode 100644 index 2ed32c30..00000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/NumericFieldWidget.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.cleanroommc.modularui.widgets.textfield; - -import com.cleanroommc.modularui.ModularUI; -import com.cleanroommc.modularui.api.IMathValue; -import com.cleanroommc.modularui.api.value.IStringValue; -import com.cleanroommc.modularui.utils.math.MathBuilder; -import com.cleanroommc.modularui.value.StringValue; - -import org.jetbrains.annotations.NotNull; - -import java.text.DecimalFormat; - -public abstract class NumericFieldWidget> extends TextFieldWidget { - - protected final DecimalFormat format = new DecimalFormat(); - private String mathFailMessage = null; - - public double parse(String num) { - try { - IMathValue mathValue = MathBuilder.INSTANCE.parse(num); - double ret = mathValue.doubleValue(); - this.mathFailMessage = null; - return ret; - } catch (MathBuilder.ParseException | IMathValue.EvaluateException e) { - this.mathFailMessage = e.getMessage(); - } catch (Exception e) { - this.mathFailMessage = "Internal crash"; - ModularUI.LOGGER.catching(e); - } - return getNumberFromValue().doubleValue(); - } - - public IStringValue createMathFailMessageValue() { - return new StringValue.Dynamic(() -> this.mathFailMessage, val -> this.mathFailMessage = val); - } - - @NotNull - @Override - protected final String getDisplayTextFromValue() { - return format.format(getNumberFromValue()); - } - - protected abstract Number getNumberFromValue(); -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java deleted file mode 100644 index 30cfbd59..00000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/StringTextFieldWidget.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.cleanroommc.modularui.widgets.textfield; - -import com.cleanroommc.modularui.api.value.IStringValue; -import com.cleanroommc.modularui.value.StringValue; -import com.cleanroommc.modularui.value.sync.SyncHandler; - -import org.jetbrains.annotations.NotNull; - -import java.util.function.Function; - -public class StringTextFieldWidget extends TextFieldWidget { - - private IStringValue stringValue; - private Function validator = val -> val; - - @Override - protected void setupValueIfNull() { - if (this.stringValue == null) { - this.stringValue = new StringValue(""); - } - } - - @Override - protected boolean checkAndSetSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof IStringValue val) { - this.stringValue = val; - return true; - } - return false; - } - - @NotNull - @Override - protected String getDisplayTextFromValue() { - return this.stringValue.getStringValue(); - } - - @Override - protected void parseDisplayText(String text) { - String validatedText = this.validator.apply(text); - this.stringValue.setStringValue(validatedText); - } - - public StringTextFieldWidget value(IStringValue value) { - this.stringValue = value; - setValue(value); - return this; - } - - public StringTextFieldWidget setValidator(Function validator) { - this.validator = validator; - return this; - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index 3a91be45..2ec56a90 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -1,54 +1,64 @@ package com.cleanroommc.modularui.widgets.textfield; +import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.api.IMathValue; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.value.IStringValue; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.utils.math.Constant; +import com.cleanroommc.modularui.utils.math.MathBuilder; +import com.cleanroommc.modularui.value.StringValue; import com.cleanroommc.modularui.value.sync.SyncHandler; import com.cleanroommc.modularui.value.sync.ValueSyncHandler; import org.jetbrains.annotations.NotNull; import java.awt.*; +import java.text.ParsePosition; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.regex.Pattern; /** * Text input widget with one line only. Can be synced between client and server. Can handle text validation. - *

- * Child classes are expected to have Value bound to it. It's capable of doing anything to displayed text - * while keeping value consistent. */ -public abstract class TextFieldWidget< W extends TextFieldWidget> extends BaseTextFieldWidget { +public class TextFieldWidget extends BaseTextFieldWidget { - protected boolean changedMarkedColor = false; - - /** - * Called on widget init. Set default value if it's not set by user, so that it can be called anytime. - */ - protected abstract void setupValueIfNull(); + private IStringValue stringValue; + private Function validator = val -> val; + private boolean numbers = false; + private String mathFailMessage = null; + private double defaultNumber = 0; - /** - * Called when initializing sync handlers. If supplied sync handler can satisfy the requirement, save it and return true. - */ - protected abstract boolean checkAndSetSyncHandler(SyncHandler syncHandler); + protected boolean changedMarkedColor = false; - /** - * Return text to display derived from value. - */ - @NotNull - protected abstract String getDisplayTextFromValue(); + public IMathValue parse(String num) { + try { + IMathValue ret = MathBuilder.INSTANCE.parse(num); + this.mathFailMessage = null; + return ret; + } catch (MathBuilder.ParseException e) { + this.mathFailMessage = e.getMessage(); + } catch (Exception e) { + ModularUI.LOGGER.catching(e); + } + return new Constant(this.defaultNumber); + } - /** - * Parse supplied display text and store to the value. - */ - protected abstract void parseDisplayText(String text); + public IStringValue createMathFailMessageValue() { + return new StringValue.Dynamic(() -> this.mathFailMessage, val -> this.mathFailMessage = val); + } @Override public void onInit() { super.onInit(); - setupValueIfNull(); - setText(getDisplayTextFromValue()); + if (this.stringValue == null) { + this.stringValue = new StringValue(""); + } + setText(this.stringValue.getStringValue()); if (!hasTooltip()) { tooltipBuilder(tooltip -> tooltip.addLine(IKey.str(getText()))); } @@ -66,11 +76,12 @@ public int getMarkedColor() { } @Override - public final boolean isValidSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof ValueSyncHandler valueSyncHandler && checkAndSetSyncHandler(syncHandler)) { + public boolean isValidSyncHandler(SyncHandler syncHandler) { + if (syncHandler instanceof IStringValue iStringValue && syncHandler instanceof ValueSyncHandler valueSyncHandler) { + this.stringValue = iStringValue; valueSyncHandler.setChangeListener(() -> { markTooltipDirty(); - setText(getDisplayTextFromValue()); + setText(this.stringValue.getValue().toString()); }); return true; } @@ -81,7 +92,7 @@ public final boolean isValidSyncHandler(SyncHandler syncHandler) { public void onUpdate() { super.onUpdate(); if (!isFocused()) { - String s = getDisplayTextFromValue(); + String s = this.stringValue.getStringValue(); if (!getText().equals(s)) { setText(s); } @@ -137,15 +148,14 @@ public void onFocus(ModularGuiContext context) { public void onRemoveFocus(ModularGuiContext context) { super.onRemoveFocus(context); if (this.handler.getText().isEmpty()) { - parseDisplayText(""); - this.handler.getText().add(getDisplayTextFromValue()); + this.handler.getText().add(this.validator.apply("")); } else if (this.handler.getText().size() == 1) { - parseDisplayText(this.handler.getText().get(0)); - this.handler.getText().set(0, getDisplayTextFromValue()); + this.handler.getText().set(0, this.validator.apply(this.handler.getText().get(0))); markTooltipDirty(); } else { throw new IllegalStateException("TextFieldWidget can only have one line!"); } + this.stringValue.setStringValue(this.numbers ? format.parse(getText(), new ParsePosition(0)).toString() : getText()); } @Override @@ -153,25 +163,112 @@ public boolean canHover() { return true; } - public W setMaxLength(int maxLength) { + public TextFieldWidget setMaxLength(int maxLength) { this.handler.setMaxCharacters(maxLength); - return getThis(); + return this; } - public W setPattern(Pattern pattern) { + public TextFieldWidget setPattern(Pattern pattern) { this.handler.setPattern(pattern); - return getThis(); + return this; } - public W setTextColor(int textColor) { + public TextFieldWidget setTextColor(int textColor) { this.renderer.setColor(textColor); this.changedTextColor = true; - return getThis(); + return this; } - public W setMarkedColor(int color) { + public TextFieldWidget setMarkedColor(int color) { this.renderer.setMarkedColor(color); this.changedMarkedColor = true; - return getThis(); + return this; + } + + public TextFieldWidget setValidator(Function validator) { + this.validator = validator; + return this; + } + + public TextFieldWidget setNumbersLong(Function validator) { + this.numbers = true; + setValidator(val -> { + long num; + if (val.isEmpty()) { + num = (long) this.defaultNumber; + } else { + try { + num = (long) parse(val).doubleValue(); + } catch (IMathValue.EvaluateException e) { + this.mathFailMessage = e.getMessage(); + num = (long) this.defaultNumber; + } + } + return format.format(validator.apply(num)); + }); + return this; + } + + public TextFieldWidget setNumbers(Function validator) { + this.numbers = true; + return setValidator(val -> { + int num; + if (val.isEmpty()) { + num = (int) this.defaultNumber; + } else { + try { + num = (int) parse(val).doubleValue(); + } catch (IMathValue.EvaluateException e) { + this.mathFailMessage = e.getMessage(); + num = (int) this.defaultNumber; + } + } + return format.format(validator.apply(num)); + }); + } + + public TextFieldWidget setNumbersDouble(Function validator) { + this.numbers = true; + return setValidator(val -> { + double num; + if (val.isEmpty()) { + num = this.defaultNumber; + } else { + try { + num = parse(val).doubleValue(); + } catch (IMathValue.EvaluateException e) { + this.mathFailMessage = e.getMessage(); + num = this.defaultNumber; + } + } + return format.format(validator.apply(num)); + }); + } + + public TextFieldWidget setNumbers(Supplier min, Supplier max) { + return setNumbers(val -> Math.min(max.get(), Math.max(min.get(), val))); + } + + public TextFieldWidget setNumbersLong(Supplier min, Supplier max) { + return setNumbersLong(val -> Math.min(max.get(), Math.max(min.get(), val))); + } + + public TextFieldWidget setNumbers(int min, int max) { + return setNumbers(val -> Math.min(max, Math.max(min, val))); + } + + public TextFieldWidget setNumbers() { + return setNumbers(Integer.MIN_VALUE, Integer.MAX_VALUE); + } + + public TextFieldWidget setDefaultNumber(double defaultNumber) { + this.defaultNumber = defaultNumber; + return this; + } + + public TextFieldWidget value(IStringValue stringValue) { + this.stringValue = stringValue; + setValue(stringValue); + return this; } }