From 8d67eeed924614e90b164675f2654fe407b6d484 Mon Sep 17 00:00:00 2001 From: YannickMG Date: Mon, 19 May 2025 21:25:59 -0400 Subject: [PATCH] Add TextFieldWidget::setFormatAsInteger --- .../cleanroommc/modularui/test/TestTile.java | 14 ++- .../widgets/textfield/TextFieldRenderer.java | 87 +++++++++++++++++-- .../widgets/textfield/TextFieldWidget.java | 7 ++ 3 files changed, 101 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index af15459a6..56355002b 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -53,6 +53,7 @@ public class TestTile extends TileEntity implements IGuiHolder, ITic private long time = 0; private int val, val2 = 0; private String value = ""; + private int intValue = 1234567; private double doubleValue = 1; private final int duration = 80; private int progress = 0; @@ -221,8 +222,17 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager, .child(new FluidSlot() .margin(2) .width(30) - .syncHandler(SyncHandlers.fluidSlot(this.fluidTankPhantom).phantom(true))) - ))) + .syncHandler(SyncHandlers.fluidSlot(this.fluidTankPhantom).phantom(true)))) + .child(new Column() + .debugName("button and slots test 3") + .coverChildren() + .alignY(Alignment.START) + .child(new TextFieldWidget() + .size(60, 20) + .value(SyncHandlers.intNumber(() -> this.intValue, val -> this.intValue = val)) + .setNumbers(0, 9999999) + .setFormatAsInteger(true) + .hintText("integer"))))) .addPage(new Column() .debugName("Slots test page") .coverChildren() diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldRenderer.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldRenderer.java index ecc7fb0fc..9a44e3dce 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldRenderer.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldRenderer.java @@ -10,17 +10,30 @@ import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + import java.awt.*; import java.awt.geom.Point2D; +import java.text.DecimalFormat; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; public class TextFieldRenderer extends TextRenderer { + private static final DecimalFormat INTEGER_FIELD_FORMAT = new DecimalFormat("#"); + private static final char groupingSeparator = INTEGER_FIELD_FORMAT.getDecimalFormatSymbols().getGroupingSeparator(); + + static { + INTEGER_FIELD_FORMAT.setGroupingUsed(true); + INTEGER_FIELD_FORMAT.setGroupingSize(3); + } protected final TextFieldHandler handler; protected int markedColor = 0x2F72A8; protected int cursorColor = 0xFFFFFFFF; protected boolean renderCursor = false; + private boolean formatAsInteger = false; public TextFieldRenderer(TextFieldHandler handler) { this.handler = handler; @@ -42,6 +55,30 @@ public void setCursorColor(int cursorColor) { this.cursorColor = cursorColor; } + @ApiStatus.Experimental + public void setFormatAsInteger(boolean formatAsInteger) { + this.formatAsInteger = formatAsInteger; + } + + @Override + public void draw(List lines) { + if (formatAsInteger) lines = decorateLines(lines); + super.draw(lines); + } + + private static @NotNull List decorateLines(List lines) { + return lines.stream().map(TextFieldRenderer::tryFormatString) + .collect(Collectors.toList()); + } + + private static @NotNull String tryFormatString(String str) { + try { + return INTEGER_FIELD_FORMAT.format(Long.parseLong(str)); + } catch (NumberFormatException e) { + return str; + } + } + @Override protected void drawMeasuredLines(List measuredLines) { drawMarked(measuredLines); @@ -96,39 +133,79 @@ public Point getCursorPos(List lines, int x, int y) { if (lines.isEmpty()) { return new Point(); } + if (formatAsInteger) lines = decorateLines(lines); List measuredLines = measureLines(lines); y -= getStartY(measuredLines.size()); int index = (int) (y / (getFontHeight())); if (index < 0) return new Point(); - if (index >= measuredLines.size()) - return new Point(measuredLines.get(measuredLines.size() - 1).getText().length(), measuredLines.size() - 1); + if (index >= measuredLines.size()) { + return new Point(getRealLength(measuredLines.get(measuredLines.size() - 1).getText()), measuredLines.size() - 1); + } Line line = measuredLines.get(index); x -= getStartX(line.getWidth()); if (line.getWidth() <= 0) return new Point(0, index); - if (line.getWidth() < x) return new Point(line.getText().length(), index); + if (line.getWidth() < x) { + return new Point(getRealLength(line.getText()), index); + } float currentX = 0; + int ignoredChars = 0; for (int i = 0; i < line.getText().length(); i++) { char c = line.getText().charAt(i); float charWidth = getFontRenderer().getCharWidth(c) * this.scale; currentX += charWidth; + if (isIgnoredChar(c)) ignoredChars++; if (currentX >= x) { // dist with current letter < dist without current letter -> next letter pos if (Math.abs(currentX - x) < Math.abs(currentX - charWidth - x)) i++; - return new Point(i, index); + return new Point(i - ignoredChars, index); } } return new Point(); } + /** + * Whether the given character should be ignored for cursor positioning purposes + */ + @ApiStatus.Experimental + protected boolean isIgnoredChar(int c) { + return formatAsInteger && c == groupingSeparator; + } + + private int getRealLength(String text) { + int length = text.length(); + if (formatAsInteger) length -= (int) text.chars().filter(this::isIgnoredChar).count(); + return length; + } + public Point2D.Float getPosOf(List measuredLines, Point cursorPos) { if (measuredLines.isEmpty()) { return new Point2D.Float(getStartX(0), getStartYOfLines(1)); } Line line = measuredLines.get(cursorPos.y); - String sub = line.getText().substring(0, Math.min(line.getText().length(), cursorPos.x)); + String sub = getStringBeforeCursor(line, cursorPos); return new Point2D.Float(getStartX(line.getWidth()) + getFontRenderer().getStringWidth(sub) * this.scale, getStartYOfLines(measuredLines.size()) + cursorPos.y * getFontHeight()); } + private @NotNull String getStringBeforeCursor(Line line, Point cursorPos) { + String text = line.getText(); + String sub = text.substring(0, Math.min(text.length(), cursorPos.x)); + if (formatAsInteger) { + int i = 0; + int ignoredChars = 0; + while (i < sub.length() && i + ignoredChars < text.length()) { + if (isIgnoredChar(text.charAt(i + ignoredChars))) { + ignoredChars++; + } else { + i++; + } + } + if (ignoredChars > 0) { + sub = sub + text.substring(sub.length(), Math.min(text.length(), cursorPos.x + ignoredChars)); + } + } + return sub; + } + @SideOnly(Side.CLIENT) public void drawMarked(float y0, float x0, float x1) { y0 -= 1; 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 8167d127e..659b6f4e2 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -13,6 +13,7 @@ import com.cleanroommc.modularui.value.sync.SyncHandler; import com.cleanroommc.modularui.value.sync.ValueSyncHandler; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.awt.*; @@ -232,6 +233,12 @@ public TextFieldWidget setDefaultNumber(double defaultNumber) { return this; } + @ApiStatus.Experimental + public TextFieldWidget setFormatAsInteger(boolean formatAsInteger) { + this.renderer.setFormatAsInteger(formatAsInteger); + return getThis(); + } + public TextFieldWidget value(IStringValue stringValue) { this.stringValue = stringValue; setValue(stringValue);