2022-04-29 15:48:27 +02:00
|
|
|
package io.gitlab.jfronny.libjf.config.impl.client.gui;
|
|
|
|
|
2022-04-29 16:07:12 +02:00
|
|
|
import io.gitlab.jfronny.commons.throwable.Try;
|
2022-04-29 15:48:27 +02:00
|
|
|
import io.gitlab.jfronny.commons.tuple.Tuple;
|
|
|
|
import io.gitlab.jfronny.libjf.LibJf;
|
|
|
|
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
|
|
|
|
import io.gitlab.jfronny.libjf.config.api.Entry;
|
|
|
|
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
|
2022-04-29 16:07:12 +02:00
|
|
|
import io.gitlab.jfronny.libjf.config.api.WidgetFactory;
|
2022-04-29 15:48:27 +02:00
|
|
|
import io.gitlab.jfronny.libjf.config.impl.EntryInfoImpl;
|
|
|
|
import net.fabricmc.api.EnvType;
|
|
|
|
import net.fabricmc.api.Environment;
|
|
|
|
import net.minecraft.client.gui.widget.ButtonWidget;
|
|
|
|
import net.minecraft.client.gui.widget.TextFieldWidget;
|
|
|
|
import net.minecraft.text.LiteralText;
|
|
|
|
import net.minecraft.text.Text;
|
|
|
|
import net.minecraft.text.TranslatableText;
|
|
|
|
import net.minecraft.util.Formatting;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.function.Function;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
|
|
public class EntryInfoWidgetBuilder {
|
|
|
|
private static final Pattern INTEGER_ONLY = Pattern.compile("(-?\\d*)");
|
|
|
|
private static final Pattern DECIMAL_ONLY = Pattern.compile("-?(\\d+\\.?\\d*|\\d*\\.?\\d+|\\.)");
|
|
|
|
|
|
|
|
public static List<WidgetState<?>> buildWidgets(ConfigInstance config) {
|
|
|
|
WidgetState<?> state;
|
|
|
|
List<WidgetState<?>> knownStates = new ArrayList<>();
|
|
|
|
for (EntryInfo<?> info : config.getEntries()) {
|
|
|
|
if ((state = initEntry(config, info, knownStates)) != null) {
|
|
|
|
knownStates.add(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return knownStates;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static <T> WidgetState<T> initEntry(ConfigInstance config, EntryInfo<T> info, List<WidgetState<?>> knownStates) {
|
|
|
|
Class<T> type = info.getValueType();
|
|
|
|
WidgetState<T> state = new WidgetState<>();
|
2022-04-29 16:07:12 +02:00
|
|
|
WidgetFactory factory;
|
2022-04-29 15:48:27 +02:00
|
|
|
|
2022-04-29 16:07:12 +02:00
|
|
|
if (type == int.class || type == Integer.class) factory = textField(info, state, INTEGER_ONLY, Integer::parseInt, true, info.getMinValue(), info.getMaxValue());
|
|
|
|
else if (type == float.class || type == Float.class) factory = textField(info, state, DECIMAL_ONLY, Float::parseFloat, false, info.getMinValue(), info.getMaxValue());
|
|
|
|
else if (type == double.class || type == Double.class) factory = textField(info, state, DECIMAL_ONLY, Double::parseDouble, false, info.getMinValue(), info.getMaxValue());
|
|
|
|
else if (type == String.class) factory = textField(info, state, null, String::length, true, Math.min(info.getMinValue(),0), Math.max(info.getMaxValue(),1));
|
2022-04-29 15:48:27 +02:00
|
|
|
else if (type == boolean.class || type == Boolean.class) {
|
2022-04-29 16:07:12 +02:00
|
|
|
factory = toggle(info, state,
|
2022-04-29 15:48:27 +02:00
|
|
|
value -> !(Boolean) value,
|
|
|
|
value -> new LiteralText((Boolean) value ? "True" : "False").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED));
|
|
|
|
} else if (type.isEnum()) {
|
|
|
|
List<T> values = Arrays.asList(info.getValueType().getEnumConstants());
|
2022-04-29 16:07:12 +02:00
|
|
|
factory = toggle(info, state, value -> {
|
2022-04-29 15:48:27 +02:00
|
|
|
int index = values.indexOf(value) + 1;
|
|
|
|
return values.get(index >= values.size() ? 0 : index);
|
|
|
|
}, value -> new TranslatableText(config.getModId() + ".jfconfig.enum." + type.getSimpleName() + "." + state.cachedValue));
|
|
|
|
} else {
|
|
|
|
LibJf.LOGGER.error("Invalid entry type in " + info.getName() + ": " + type.getName());
|
2022-04-29 16:07:12 +02:00
|
|
|
factory = ((screenWidth, textRenderer, done) -> new WidgetFactory.Widget(() -> {}, new ButtonWidget(-10, 0, 0, 0, Text.of(""), null)));
|
2022-04-29 15:48:27 +02:00
|
|
|
}
|
|
|
|
|
2022-04-29 16:07:12 +02:00
|
|
|
Try.orThrow(() -> state.initialize(info, knownStates, factory));
|
2022-04-29 15:48:27 +02:00
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2022-04-29 16:07:12 +02:00
|
|
|
private static <T> WidgetFactory toggle(EntryInfo<T> info, WidgetState<T> state, Function<Object, Object> increment, Function<T, Text> valueTextifier) {
|
|
|
|
return (screenWidth, textRenderer, done) -> {
|
|
|
|
ButtonWidget button = new ButtonWidget(screenWidth - 110, 0, info.getWidth(), 20, valueTextifier.apply(state.cachedValue), btn -> {
|
2022-04-29 15:48:27 +02:00
|
|
|
state.updateCache((T) increment.apply(state.cachedValue));
|
|
|
|
btn.setMessage(valueTextifier.apply(state.cachedValue));
|
|
|
|
});
|
2022-04-29 16:07:12 +02:00
|
|
|
return new WidgetFactory.Widget(() -> button.setMessage(valueTextifier.apply(state.cachedValue)), button);
|
|
|
|
};
|
2022-04-29 15:48:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param info The entry to generate a widget for
|
|
|
|
* @param state The state representation of this widget
|
|
|
|
* @param pattern The pattern a valid value must abide to
|
|
|
|
* @param sizeFetcher A function to get a number for size constraints
|
|
|
|
* @param wholeNumber Whether size constraints are whole numbers
|
|
|
|
* @param min The minimum size of a valid value
|
|
|
|
* @param max The maximum size of a valid value
|
|
|
|
*/
|
2022-04-29 16:07:12 +02:00
|
|
|
private static <T> WidgetFactory textField(EntryInfo<T> info, WidgetState<T> state, Pattern pattern, Function<String, Number> sizeFetcher, boolean wholeNumber, double min, double max) {
|
2022-04-29 15:48:27 +02:00
|
|
|
boolean isNumber = pattern != null;
|
2022-04-29 16:07:12 +02:00
|
|
|
return (width, textRenderer, done) -> {
|
|
|
|
TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 110, 0, info.getWidth(), 20, null);
|
2022-04-29 15:48:27 +02:00
|
|
|
|
|
|
|
widget.setText(state.tempValue);
|
|
|
|
widget.setTextPredicate(currentInput -> {
|
|
|
|
currentInput = currentInput.trim();
|
|
|
|
if (!(currentInput.isEmpty() || !isNumber || pattern.matcher(currentInput).matches())) return false;
|
|
|
|
|
|
|
|
Number value = 0;
|
|
|
|
boolean inLimits = false;
|
|
|
|
state.error = null;
|
|
|
|
if (!(isNumber && currentInput.isEmpty()) && !currentInput.equals("-") && !currentInput.equals(".")) {
|
|
|
|
value = sizeFetcher.apply(currentInput);
|
|
|
|
inLimits = value.doubleValue() >= min && value.doubleValue() <= max;
|
|
|
|
state.error = inLimits ? null : Tuple.of(widget, new LiteralText(value.doubleValue() < min ?
|
|
|
|
"§cMinimum " + (isNumber? "value" : "length") + (wholeNumber ? " is " + (int) min : " is " + min) :
|
|
|
|
"§cMaximum " + (isNumber? "value" : "length") + (wholeNumber ? " is " + (int) max : " is " + max)));
|
|
|
|
}
|
|
|
|
|
|
|
|
state.tempValue = currentInput;
|
|
|
|
widget.setEditableColor(inLimits? 0xFFFFFFFF : 0xFFFF7777);
|
|
|
|
state.inLimits = inLimits;
|
|
|
|
done.active = state.knownStates.stream().allMatch(st -> st.inLimits);
|
|
|
|
|
|
|
|
if (inLimits) {
|
|
|
|
//Coerce.consumer(info::setValue).addHandler(e -> {}).accept(isNumber ? (T) value : (T) currentInput);
|
|
|
|
state.cachedValue = isNumber ? (T) value : (T) currentInput;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
2022-04-29 16:07:12 +02:00
|
|
|
return new WidgetFactory.Widget(() -> widget.setText(state.cachedValue == null ? "" : state.cachedValue.toString()), widget);
|
|
|
|
};
|
2022-04-29 15:48:27 +02:00
|
|
|
}
|
|
|
|
}
|