Skip to content

Commit b069b4d

Browse files
committed
rework number formatter to be more modular and better
1 parent ba619ff commit b069b4d

File tree

6 files changed

+308
-54
lines changed

6 files changed

+308
-54
lines changed

src/main/java/com/cleanroommc/modularui/test/TestTile.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager)
204204
.slot(SyncHandlers.itemSlot(this.inventory, 0).ignoreMaxStackSize(true).singletonSlotGroup()))
205205
.child(new FluidSlot()
206206
.margin(2)
207-
.width(30)
207+
.width(18)
208208
.syncHandler(SyncHandlers.fluidSlot(this.fluidTankPhantom).phantom(true)))
209209
)))
210210
.addPage(new Column()
Lines changed: 177 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,213 @@
11
package com.cleanroommc.modularui.utils;
22

3-
import org.apache.commons.lang3.StringUtils;
4-
3+
import java.math.RoundingMode;
54
import java.text.DecimalFormat;
5+
import java.text.DecimalFormatSymbols;
66

7+
/**
8+
* A number formatter for large, small, positive, negative, integers and decimals with adjustable format parameters.
9+
* Test results can be seen at test/java/com.cleanroommc.modularui.FormatTest.
10+
*/
711
public class NumberFormat {
812

9-
public static final DecimalFormat FORMAT = new DecimalFormat("0.###");
13+
public static final Params DEFAULT = paramsBuilder()
14+
.roundingMode(RoundingMode.HALF_UP)
15+
.maxLength(4)
16+
.considerMinusForLength(false)
17+
.considerDecimalSeparatorForLength(false)
18+
.considerOnlyDecimalsForLength(false)
19+
.considerSuffixForLength(true)
20+
.build();
21+
22+
public static final Params AMOUNT_TEXT = DEFAULT.copyToBuilder()
23+
.roundingMode(RoundingMode.DOWN)
24+
.build();
1025

1126
private static final double[] FACTORS_HIGH = {1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24};
1227
private static final double[] FACTORS_LOW = {1e-3, 1e-6, 1e-9, 1e-12, 1e-15, 1e-18, 1e-21, 1e-24};
28+
//private static final double[] ALL_FACTORS = new double[FACTORS_HIGH.length + FACTORS_LOW.length];
1329
private static final String[] SUFFIX_HIGH = {"k", "M", "G", "T", "P", "E", "Z", "Y"};
14-
private static final String[] SUFFIX_LOW = {"m", "u", "n", "p", "f", "a", "z", "y"};
30+
private static final String[] SUFFIX_LOW = {"m", "µ", "n", "p", "f", "a", "z", "y"};
31+
private static final String NO_SUFFIX = "";
32+
33+
/*static {
34+
for (int i = 0; i < FACTORS_LOW.length; i++) {
35+
ALL_FACTORS[i] = FACTORS_LOW[FACTORS_LOW.length - 1 - i];
36+
}
37+
System.arraycopy(FACTORS_HIGH, 0, ALL_FACTORS, FACTORS_LOW.length, FACTORS_HIGH.length);
38+
}*/
1539

16-
public static String formatWithMaxDigits(double value) {
17-
return format(value, 4, true);
40+
public static Params params(DecimalFormat format, int maxLength, boolean considerOnlyDecimalsForLength, boolean considerDecimalSeparatorForLength, boolean considerMinusForLength, boolean considerSuffixForLength) {
41+
return new Params(format, maxLength, considerOnlyDecimalsForLength, considerDecimalSeparatorForLength, considerMinusForLength, considerSuffixForLength);
1842
}
1943

20-
public static String formatWithMaxDigits(double value, int maxDigits) {
21-
return format(value, maxDigits, true);
44+
public static ParamsBuilder paramsBuilder() {
45+
return new ParamsBuilder();
2246
}
2347

24-
public static String formatWithMaxDecimals(double value, int decimals) {
25-
return format(value, decimals, false);
48+
public static class Params {
49+
public final DecimalFormat format;
50+
public final int maxLength;
51+
public final boolean considerOnlyDecimalsForLength;
52+
public final boolean considerDecimalSeparatorForLength;
53+
public final boolean considerMinusForLength;
54+
public final boolean considerSuffixForLength;
55+
56+
public Params(DecimalFormat format, int maxLength, boolean considerOnlyDecimalsForLength, boolean considerDecimalSeparatorForLength, boolean considerMinusForLength, boolean considerSuffixForLength) {
57+
this.format = format;
58+
this.maxLength = maxLength;
59+
this.considerOnlyDecimalsForLength = considerOnlyDecimalsForLength;
60+
this.considerDecimalSeparatorForLength = considerDecimalSeparatorForLength;
61+
this.considerMinusForLength = considerMinusForLength;
62+
this.considerSuffixForLength = considerSuffixForLength;
63+
if (!this.considerOnlyDecimalsForLength && this.maxLength < 4) {
64+
throw new IllegalArgumentException("Max length must be at least 4 characters");
65+
}
66+
}
67+
68+
public ParamsBuilder copyToBuilder() {
69+
DecimalFormat format = (DecimalFormat) this.format.clone();
70+
format.setMinimumFractionDigits(this.format.getMinimumFractionDigits());
71+
format.setMaximumFractionDigits(this.format.getMaximumFractionDigits());
72+
format.setMinimumIntegerDigits(this.format.getMinimumIntegerDigits());
73+
format.setMaximumIntegerDigits(this.format.getMaximumIntegerDigits());
74+
format.setRoundingMode(this.format.getRoundingMode());
75+
format.setGroupingSize(this.format.getGroupingSize());
76+
format.setGroupingUsed(this.format.isGroupingUsed());
77+
return new ParamsBuilder()
78+
.format(format)
79+
.maxLength(this.maxLength)
80+
.considerOnlyDecimalsForLength(this.considerOnlyDecimalsForLength)
81+
.considerDecimalSeparatorForLength(this.considerDecimalSeparatorForLength)
82+
.considerMinusForLength(this.considerMinusForLength)
83+
.considerSuffixForLength(this.considerSuffixForLength);
84+
}
2685
}
2786

28-
private static String format(double value, int precision, boolean maxDigits) {
87+
public static class ParamsBuilder {
88+
89+
private DecimalFormat format;
90+
private int maxLength;
91+
private boolean considerOnlyDecimalsForLength;
92+
private boolean considerDecimalSeparatorForLength;
93+
private boolean considerMinusForLength;
94+
private boolean considerSuffixForLength;
95+
96+
private DecimalFormat checkFormat() {
97+
if (this.format == null) {
98+
this.format = new DecimalFormat("0.###");
99+
}
100+
return this.format;
101+
}
102+
103+
public ParamsBuilder format(DecimalFormat format) {
104+
this.format = format;
105+
return this;
106+
}
107+
108+
public ParamsBuilder roundingMode(RoundingMode roundingMode) {
109+
checkFormat().setRoundingMode(roundingMode);
110+
return this;
111+
}
112+
113+
public ParamsBuilder decimalFormatSymbols(DecimalFormatSymbols symbols) {
114+
checkFormat().setDecimalFormatSymbols(symbols);
115+
return this;
116+
}
117+
118+
public ParamsBuilder decimalSeparator(char c) {
119+
DecimalFormatSymbols symbols = checkFormat().getDecimalFormatSymbols();
120+
symbols.setDecimalSeparator('.');
121+
checkFormat().setDecimalFormatSymbols(symbols);
122+
return this;
123+
}
124+
125+
public ParamsBuilder maxLength(int maxLength) {
126+
this.maxLength = maxLength;
127+
return this;
128+
}
129+
130+
public ParamsBuilder considerOnlyDecimalsForLength(boolean considerOnlyDecimalsForLength) {
131+
this.considerOnlyDecimalsForLength = considerOnlyDecimalsForLength;
132+
return this;
133+
}
134+
135+
public ParamsBuilder considerDecimalSeparatorForLength(boolean considerDecimalSeparatorForLength) {
136+
this.considerDecimalSeparatorForLength = considerDecimalSeparatorForLength;
137+
return this;
138+
}
139+
140+
public ParamsBuilder considerMinusForLength(boolean considerMinusForLength) {
141+
this.considerMinusForLength = considerMinusForLength;
142+
return this;
143+
}
144+
145+
public ParamsBuilder considerSuffixForLength(boolean considerSuffixForLength) {
146+
this.considerSuffixForLength = considerSuffixForLength;
147+
return this;
148+
}
149+
150+
public Params build() {
151+
return new Params(checkFormat(), this.maxLength, this.considerOnlyDecimalsForLength, this.considerDecimalSeparatorForLength, this.considerMinusForLength, this.considerSuffixForLength);
152+
}
153+
}
154+
155+
public static String format(double number, Params params) {
156+
boolean negative = number < 0;
157+
int maxLength = params.maxLength;
158+
if (negative) {
159+
number = -number;
160+
if (params.considerMinusForLength) maxLength--;
161+
}
162+
String formattedNumber = formatInternal(number, maxLength, params);
163+
if (negative) formattedNumber = "-" + formattedNumber;
164+
return formattedNumber;
165+
}
166+
167+
private static String formatInternal(double number, int maxLength, Params params) {
29168
int n = FACTORS_HIGH.length - 1;
30-
if (value >= 1000) {
169+
String suffix = NO_SUFFIX;
170+
if (number > 9999) {
31171
int index;
32172
for (index = 0; index < n; index++) {
33-
if (value < FACTORS_HIGH[index + 1]) {
173+
if (number < FACTORS_HIGH[index + 1]) {
34174
break;
35175
}
36176
}
37-
return formatToString(value / FACTORS_HIGH[index], precision, maxDigits, SUFFIX_HIGH[index]);
38-
}
39-
if (value < 1) {
177+
number /= FACTORS_HIGH[index];
178+
suffix = SUFFIX_HIGH[index];
179+
} else if (number < 1) {
40180
int index;
41181
for (index = 0; index < n; index++) {
42-
if (value >= FACTORS_LOW[index]) {
182+
if (number >= FACTORS_LOW[index]) {
43183
break;
44184
}
45185
}
46-
return formatToString(value / FACTORS_LOW[index], precision, maxDigits, SUFFIX_LOW[index]);
186+
number /= FACTORS_LOW[index];
187+
suffix = SUFFIX_LOW[index];
47188
}
48-
return formatToString(value, precision, maxDigits, StringUtils.EMPTY);
189+
return formatToString(number, suffix, maxLength, params);
49190
}
50191

51-
private static String formatToString(double value, int precision, boolean maxDigits, String suffix) {
52-
if (maxDigits) {
53-
String[] parts = String.valueOf(value).split("\\.");
54-
if (parts.length > 1) {
55-
precision -= parts[0].length();
56-
}
192+
private static String formatToString(double value, String suffix, int maxLength, Params params) {
193+
if (params.considerSuffixForLength && !suffix.isEmpty()) {
194+
maxLength -= suffix.length();
57195
}
58-
FORMAT.setMaximumFractionDigits(precision);
59-
return FORMAT.format(value) + suffix;
60-
}
61-
62-
public static double getFactorForSuffix(char suffix) {
63-
for (int i = 0; i < SUFFIX_HIGH.length; i++) {
64-
String s = SUFFIX_HIGH[i];
65-
if (s.charAt(0) == suffix) return FACTORS_HIGH[i];
196+
if (params.considerDecimalSeparatorForLength) {
197+
maxLength--;
66198
}
67-
for (int i = 0; i < SUFFIX_LOW.length; i++) {
68-
String s = SUFFIX_LOW[i];
69-
if (s.charAt(0) == suffix) return FACTORS_LOW[i];
199+
if (!params.considerOnlyDecimalsForLength) {
200+
if (value % 1 > 0) {
201+
int intDigits = (int) (Math.log10(Math.floor(value)) + 1);
202+
maxLength -= intDigits;
203+
}
70204
}
71-
return 0;
72-
}
73-
74-
public static double getFactorForSuffix(String suffix) {
75-
return getFactorForSuffix(suffix.charAt(0));
205+
int m1 = params.format.getMaximumFractionDigits();
206+
int m2 = params.format.getMinimumFractionDigits();
207+
params.format.setMaximumFractionDigits(maxLength);
208+
String s = params.format.format(value) + suffix;
209+
params.format.setMaximumFractionDigits(m1);
210+
params.format.setMinimumFractionDigits(m2);
211+
return s;
76212
}
77213
}

src/main/java/com/cleanroommc/modularui/widgets/FluidSlot.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,21 @@
3333
import org.jetbrains.annotations.Nullable;
3434
import org.lwjgl.input.Keyboard;
3535

36+
import java.text.DecimalFormat;
37+
3638
public class FluidSlot extends Widget<FluidSlot> implements Interactable, JeiGhostIngredientSlot<FluidStack>, JeiIngredientProvider {
3739

3840
public static final int DEFAULT_SIZE = 18;
39-
40-
private static final String UNIT_BUCKET = "B";
41-
private static final String UNIT_LITER = "L";
42-
41+
public static final String UNIT_BUCKET = "B";
42+
public static final String UNIT_LITER = "L";
43+
private static final DecimalFormat TOOLTIP_FORMAT = new DecimalFormat("#.##");
4344
private static final IFluidTank EMPTY = new FluidTank(0);
4445

46+
static {
47+
TOOLTIP_FORMAT.setGroupingUsed(true);
48+
TOOLTIP_FORMAT.setGroupingSize(3);
49+
}
50+
4551
private final TextRenderer textRenderer = new TextRenderer();
4652
private FluidSlotSyncHandler syncHandler;
4753
private int contentOffsetX = 1, contentOffsetY = 1;
@@ -61,7 +67,7 @@ public FluidSlot() {
6167
if (this.syncHandler.isPhantom()) {
6268
if (fluid != null) {
6369
if (this.syncHandler.controlsAmount()) {
64-
tooltip.addLine(IKey.lang("modularui.fluid.phantom.amount", formatFluidAmount(fluid.amount), getBaseUnit()));
70+
tooltip.addLine(IKey.lang("modularui.fluid.phantom.amount", formatFluidTooltipAmount(fluid.amount), getBaseUnit()));
6571
}
6672
} else {
6773
tooltip.addLine(IKey.lang("modularui.fluid.empty"));
@@ -71,7 +77,7 @@ public FluidSlot() {
7177
}
7278
} else {
7379
if (fluid != null) {
74-
tooltip.addLine(IKey.lang("modularui.fluid.amount", formatFluidAmount(fluid.amount), formatFluidAmount(fluidTank.getCapacity()), getBaseUnit()));
80+
tooltip.addLine(IKey.lang("modularui.fluid.amount", formatFluidTooltipAmount(fluid.amount), formatFluidTooltipAmount(fluidTank.getCapacity()), getBaseUnit()));
7581
addAdditionalFluidInfo(tooltip, fluid);
7682
} else {
7783
tooltip.addLine(IKey.lang("modularui.fluid.empty"));
@@ -96,9 +102,9 @@ public FluidSlot() {
96102

97103
public void addAdditionalFluidInfo(RichTooltip tooltip, FluidStack fluidStack) {}
98104

99-
public String formatFluidAmount(double amount) {
100-
NumberFormat.FORMAT.setMaximumFractionDigits(3);
101-
return NumberFormat.FORMAT.format(getBaseUnitAmount(amount));
105+
public String formatFluidTooltipAmount(double amount) {
106+
// the tooltip show the full number
107+
return TOOLTIP_FORMAT.format(amount) + " " + getBaseUnitBaseSuffix();
102108
}
103109

104110
protected double getBaseUnitAmount(double amount) {
@@ -109,6 +115,10 @@ protected String getBaseUnit() {
109115
return UNIT_BUCKET;
110116
}
111117

118+
protected String getBaseUnitBaseSuffix() {
119+
return "m";
120+
}
121+
112122
@Override
113123
public void onInit() {
114124
this.textRenderer.setShadow(true);
@@ -141,7 +151,7 @@ public void draw(ModularGuiContext context, WidgetTheme widgetTheme) {
141151
this.overlayTexture.drawAtZero(context, getArea(), widgetTheme);
142152
}
143153
if (content != null && this.syncHandler.controlsAmount()) {
144-
String s = NumberFormat.formatWithMaxDigits(getBaseUnitAmount(content.amount)) + getBaseUnit();
154+
String s = NumberFormat.format(getBaseUnitAmount(content.amount), NumberFormat.AMOUNT_TEXT) + getBaseUnit();
145155
this.textRenderer.setAlignment(Alignment.CenterRight, getArea().width - this.contentOffsetX - 1f);
146156
this.textRenderer.setPos((int) (this.contentOffsetX + 0.5f), (int) (getArea().height - 5.5f));
147157
this.textRenderer.draw(s);

src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ private void drawSlot(Slot slotIn) {
248248
}
249249
// render the amount overlay
250250
if (amount > 1 || format != null) {
251-
String amountText = NumberFormat.formatWithMaxDigits(amount);
251+
String amountText = NumberFormat.format(amount, NumberFormat.AMOUNT_TEXT);
252252
if (format != null) {
253253
amountText = format + amountText;
254254
}

src/main/resources/assets/modularui/lang/en_us.lang

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ modularui.decrement.tooltip=§7Click to decrease the number by §6%d§7\nHold sh
55
modularui.amount=§9Amount: %,d
66
modularui.fluid.empty=Empty
77
modularui.fluid.amount=§9Amount: %s/%s %s
8-
modularui.fluid.phantom.amount=§9Amount: %s %s
8+
modularui.fluid.phantom.amount=§9Amount: %s%s
99
modularui.fluid.phantom.control=§7Scroll wheel up increases amount, down decreases.\n§7Shift[§6x10§7],Ctrl[§ex100§7],Alt[§ax1000§7]\n§7Right click increases amount, left click decreases.\n§7Shift + left click to clear.
1010
modularui.item.phantom.control=§7Scroll wheel up increases amount, down decreases.\n§7Shift[§6x4§7],Ctrl[§ex16§7],Alt[§ax64§7]\n§7Right click increases amount, left click decreases.\n§7Shift + left click to clear.
1111

0 commit comments

Comments
 (0)