More universal config (works with java 8 & 16)

This commit is contained in:
coolGi2007
2021-12-28 10:50:32 +00:00
parent 4a64ce0bed
commit 699d27afd2
2 changed files with 108 additions and 105 deletions
@@ -15,6 +15,7 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
// Logger (for debug stuff)
import org.apache.logging.log4j.LogManager;
@@ -41,7 +42,6 @@ import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.ContainerObjectSelectionList;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
@@ -49,13 +49,14 @@ import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.client.resources.language.I18n; // translation
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.narration.NarratableEntry; // Remove in 1.16
/**
* Based upon TinyConfig
* https://github.com/Minenash/TinyConfig
*
* This config should work for both Fabric and Forge as long as you use Mojang mappings
*
*
* Credits to Motschen
*
* @author coolGi2007
@@ -78,26 +79,26 @@ public abstract class ConfigGui
The buttons that don't show are still loaded but just not rendered
The screen with is set to double so the scroll bar doesn't show
*/
private static final Pattern INTEGER_ONLY_REGEX = Pattern.compile("(-?[0-9]*)");
private static final Pattern DECIMAL_ONLY_REGEX = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)");
private static final List<EntryInfo> entries = new ArrayList<>();
// Change these to your own mod
private static final String MOD_NAME = ModInfo.NAME; // For file saving and identifying
private static final String MOD_NAME_READABLE = ModInfo.READABLE_NAME; // For logs
// private static final Logger LOGGER = ClientApi.LOGGER; // For logs
// private static final Logger LOGGER = ClientApi.LOGGER; // For logs
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME); // For logs (this inits before ClientAPI so this is a temp fix)
//==============//
// Initializers //
//==============//
private static class ConfigScreenConfigs
{
// This contains all the configs for the configs
@@ -105,7 +106,7 @@ public abstract class ConfigGui
public static final int ButtonWidthSpacing = 5;
public static final int ResetButtonWidth = 40;
}
protected static class EntryInfo<T>
{
Field field;
@@ -130,14 +131,14 @@ public abstract class ConfigGui
}
private static Path configFilePath;
public static void init(Class<?> config)
{
Minecraft mc = Minecraft.getInstance();
configFilePath = mc.gameDirectory.toPath().resolve("config").resolve(MOD_NAME + ".toml");
initNestedClass(config);
for (EntryInfo info : entries) {
@@ -152,7 +153,7 @@ public abstract class ConfigGui
loadFromFile();
}
private static void initNestedClass(Class<?> config)
{
for (Field field : config.getFields())
@@ -164,7 +165,7 @@ public abstract class ConfigGui
if (!LodCommonMain.serverSided)
initClient(field, info);
}
if (field.isAnnotationPresent(ConfigAnnotations.Entry.class))
{
info.varClass = field.getType();
@@ -174,7 +175,7 @@ public abstract class ConfigGui
}
catch (IllegalAccessException ignored) {}
}
if (field.isAnnotationPresent(ConfigAnnotations.ScreenEntry.class))
initNestedClass(field.getType());
@@ -182,7 +183,7 @@ public abstract class ConfigGui
info.field = field;
}
}
/** This adds the buttons to the queue to be rendered */
private static void initClient(Field field, EntryInfo info)
{
@@ -190,21 +191,21 @@ public abstract class ConfigGui
ConfigAnnotations.Category category = field.getAnnotation(ConfigAnnotations.Category.class);
ConfigAnnotations.Entry entry = field.getAnnotation(ConfigAnnotations.Entry.class);
ConfigAnnotations.ScreenEntry screenEntry = field.getAnnotation(ConfigAnnotations.ScreenEntry.class);
if (entry != null)
info.width = entry.width();
else if (screenEntry != null)
info.width = screenEntry.width();
info.category = category != null ? category.value() : "";
if (entry != null)
{
if (!entry.name().equals(""))
info.name = new TranslatableComponent(entry.name());
if (fieldClass == int.class)
{
// For int
@@ -216,13 +217,13 @@ public abstract class ConfigGui
textField(info, Double::parseDouble, DECIMAL_ONLY_REGEX, entry.minValue(), entry.maxValue(), false);
}
else if (fieldClass == String.class || fieldClass == List.class)
{
{
// For string or list
info.max = entry.maxValue() == Double.MAX_VALUE ? Integer.MAX_VALUE : (int) entry.maxValue();
textField(info, String::length, null, Math.min(entry.minValue(), 0), Math.max(entry.maxValue(), 1), true);
}
else if (fieldClass == boolean.class)
{
{
// For boolean
Function<Object, Component> func = value -> new TextComponent((Boolean) value ? "True" : "False").withStyle((Boolean) value ? ChatFormatting.GREEN : ChatFormatting.RED);
info.widget = new AbstractMap.SimpleEntry<Button.OnPress, Function<Object, Component>>(button -> {
@@ -231,7 +232,7 @@ public abstract class ConfigGui
}, func);
}
else if (fieldClass.isEnum())
{
{
// For enum
List<?> values = Arrays.asList(field.getType().getEnumConstants());
Function<Object, Component> func = value -> new TranslatableComponent(MOD_NAME + ".config." + "enum." + fieldClass.getSimpleName() + "." + info.value.toString());
@@ -246,30 +247,30 @@ public abstract class ConfigGui
{
if (!screenEntry.name().equals(""))
info.name = new TranslatableComponent(screenEntry.name());
info.screenButton = true;
info.gotoScreen = (!info.category.isBlank() ? info.category + "." : "") + field.getName();
info.gotoScreen = (!info.category.isEmpty() ? info.category + "." : "") + field.getName();
}
entries.add(info);
}
/** creates a text field */
private static void textField(EntryInfo info, Function<String, Number> func, Pattern pattern, double minValue, double maxValue, boolean cast)
{
boolean isNumber = pattern != null;
info.widget = (BiFunction<EditBox, Button, Predicate<String>>) (editBox, button) -> stringValue ->
info.widget = (BiFunction<EditBox, Button, Predicate<String>>) (editBox, button) -> stringValue ->
{
stringValue = stringValue.trim();
if (!(stringValue.isBlank() || !isNumber || pattern.matcher(stringValue).matches()))
if (!(stringValue.isEmpty() || !isNumber || pattern.matcher(stringValue).matches()))
return false;
Number value = 0;
boolean inLimits = false;
info.error = null;
if (isNumber && !stringValue.isBlank() && !stringValue.equals("-") && !stringValue.equals("."))
if (isNumber && !stringValue.isEmpty() && !stringValue.equals("-") && !stringValue.equals("."))
{
value = func.apply(stringValue);
inLimits = value.doubleValue() >= minValue && value.doubleValue() <= maxValue;
@@ -277,13 +278,13 @@ public abstract class ConfigGui
"§cMinimum " + "length" + (cast ? " is " + (int) minValue : " is " + minValue) :
"§cMaximum " + "length" + (cast ? " is " + (int) maxValue : " is " + maxValue)));
}
info.tempValue = stringValue;
editBox.setTextColor(inLimits ? 0xFFFFFFFF : 0xFFFF7777);
info.inLimits = inLimits;
button.active = entries.stream().allMatch(e -> e.inLimits);
if (inLimits && info.field.getType() != List.class)
{
info.value = value;
@@ -294,18 +295,18 @@ public abstract class ConfigGui
((List<String>) info.value).add("");
((List<String>) info.value).set(info.index, Arrays.stream(info.tempValue.replace("[", "").replace("]", "").split(", ")).toList().get(0));
}
return true;
};
}
//===============//
// File Handling //
//===============//
/** Grabs what is in the config and puts it in modid.toml */
public static void saveToFile()
{
@@ -326,14 +327,14 @@ public abstract class ConfigGui
for (EntryInfo info : entries) {
if (info.field.isAnnotationPresent(ConfigAnnotations.Entry.class)) {
config.set((info.category.isBlank() ? "" : info.category + ".") + info.field.getName(), info.value);
config.set((info.category.isEmpty() ? "" : info.category + ".") + info.field.getName(), info.value);
}
}
config.save();
config.close();
}
/**
* Grabs what is in modid.toml and puts it into the config
* If the file doesn't exist then it runs saveToFile
@@ -371,8 +372,8 @@ public abstract class ConfigGui
config.close();
}
public static Screen getScreen(Screen parent, String category)
{
return new ConfigScreen(parent, category);
@@ -383,45 +384,45 @@ public abstract class ConfigGui
protected ConfigScreen(Screen parent, String category)
{
super(new TranslatableComponent(
I18n.exists(MOD_NAME + ".config" + (category.isBlank()? "." + category : "") + ".title") ?
I18n.exists(MOD_NAME + ".config" + (category.isEmpty()? "." + category : "") + ".title") ?
MOD_NAME + ".config.title" :
MOD_NAME + ".config" + (category.isBlank() ? "" : "." + category) + ".title")
MOD_NAME + ".config" + (category.isEmpty() ? "" : "." + category) + ".title")
);
this.parent = parent;
this.category = category;
this.translationPrefix = MOD_NAME + ".config.";
}
private final String translationPrefix;
private final Screen parent;
private String category;
private ConfigListWidget list;
private boolean reload = false;
// Real Time config update //
@Override
public void tick()
{
super.tick();
}
@Override
protected void init()
{
super.init();
if (!reload)
loadFromFile();
this.addRenderableWidget(new Button(this.width / 2 - 154, this.height - 28, 150, 20, CommonComponents.GUI_CANCEL, button -> {
this.addWidget(new Button(this.width / 2 - 154, this.height - 28, 150, 20, CommonComponents.GUI_CANCEL, button -> {
loadFromFile();
Objects.requireNonNull(minecraft).setScreen(parent);
}));
Button done = this.addRenderableWidget(new Button(this.width / 2 + 4, this.height - 28, 150, 20, CommonComponents.GUI_DONE, (button) -> {
Button done = this.addWidget(new Button(this.width / 2 + 4, this.height - 28, 150, 20, CommonComponents.GUI_DONE, (button) -> {
saveToFile();
Objects.requireNonNull(minecraft).setScreen(parent);
}));
this.list = new ConfigListWidget(this.minecraft, this.width * 2, this.height, 32, this.height - 32, 25);
if (this.minecraft != null && this.minecraft.level != null)
this.list.setRenderBackground(false);
@@ -430,7 +431,7 @@ public abstract class ConfigGui
{
if (info.category.matches(category) && !info.hideOption)
{
TranslatableComponent name = Objects.requireNonNullElseGet(info.name, () -> new TranslatableComponent(translationPrefix + (!info.category.isBlank() ? info.category + "." : "") + info.field.getName()));
TranslatableComponent name = (info.name == null ? new TranslatableComponent(translationPrefix + (!info.category.isEmpty() ? info.category + "." : "") + info.field.getName()) : info.name);
Button resetButton = new Button(this.width - ConfigScreenConfigs.SpaceFromRightScreen - info.width - ConfigScreenConfigs.ButtonWidthSpacing - ConfigScreenConfigs.ResetButtonWidth, 0, ConfigScreenConfigs.ResetButtonWidth, 20, new TextComponent("Reset").withStyle(ChatFormatting.RED), (button -> {
info.value = info.defaultValue;
info.tempValue = info.defaultValue.toString();
@@ -438,7 +439,7 @@ public abstract class ConfigGui
this.reload = true;
Objects.requireNonNull(minecraft).setScreen(this);
}));
if (info.widget instanceof Map.Entry)
{
Map.Entry<Button.OnPress, Function<Object, Component>> widget = (Map.Entry<Button.OnPress, Function<Object, Component>>) info.widget;
@@ -492,9 +493,9 @@ public abstract class ConfigGui
}
}
}
}
@Override
public void render(PoseStack matrices, int mouseX, int mouseY, float delta)
{
@@ -503,53 +504,53 @@ public abstract class ConfigGui
drawCenteredString(matrices, font, title, width / 2, 15, 0xFFFFFF); // Render title
// Render the tooltip only if it can find a tooltip in the language file
for (EntryInfo info : entries) {
for (EntryInfo info : entries) {
if (info.category.matches(category) && !info.hideOption) {
if (list.getHoveredButton(mouseX,mouseY).isPresent()) {
AbstractWidget buttonWidget = list.getHoveredButton(mouseX,mouseY).get();
Component text = ButtonEntry.buttonsWithText.get(buttonWidget);
TranslatableComponent name = new TranslatableComponent(this.translationPrefix + (info.category.isBlank() ? "" : info.category + ".") + info.field.getName());
String key = translationPrefix + (info.category.isBlank() ? "" : info.category + ".") + info.field.getName() + ".@tooltip";
if (list.getHoveredButton(mouseX,mouseY).isPresent()) {
AbstractWidget buttonWidget = list.getHoveredButton(mouseX,mouseY).get();
Component text = ButtonEntry.buttonsWithText.get(buttonWidget);
TranslatableComponent name = new TranslatableComponent(this.translationPrefix + (info.category.isEmpty() ? "" : info.category + ".") + info.field.getName());
String key = translationPrefix + (info.category.isEmpty() ? "" : info.category + ".") + info.field.getName() + ".@tooltip";
if (info.error != null && text.equals(name)) renderTooltip(matrices, (Component) info.error.getValue(), mouseX, mouseY);
else if (I18n.exists(key) && text.equals(name)) {
List<Component> list = new ArrayList<>();
for (String str : I18n.get(key).split("\n"))
list.add(new TextComponent(str));
if (info.error != null && text.equals(name)) renderTooltip(matrices, (Component) info.error.getValue(), mouseX, mouseY);
else if (I18n.exists(key) && text.equals(name)) {
List<Component> list = new ArrayList<>();
for (String str : I18n.get(key).split("\n"))
list.add(new TextComponent(str));
renderComponentTooltip(matrices, list, mouseX, mouseY);
}
}
}
}
}
}
}
}
super.render(matrices, mouseX, mouseY, delta);
}
}
public static class ConfigListWidget extends ContainerObjectSelectionList<ButtonEntry>
{
Font textRenderer;
public ConfigListWidget(Minecraft minecraftClient, int i, int j, int k, int l, int m)
{
super(minecraftClient, i, j, k, l, m);
this.centerListVertically = false;
textRenderer = minecraftClient.font;
}
public void addButton(AbstractWidget button, AbstractWidget resetButton, AbstractWidget indexButton, Component text)
{
this.addEntry(ButtonEntry.create(button, text, resetButton, indexButton));
}
@Override
public int getRowWidth()
{
return 10000;
}
public Optional<AbstractWidget> getHoveredButton(double mouseX, double mouseY)
{
for (ButtonEntry buttonEntry : this.children())
@@ -562,11 +563,11 @@ public abstract class ConfigGui
return Optional.empty();
}
}
public static class ButtonEntry extends ContainerObjectSelectionList.Entry<ButtonEntry>
{
private static final Font textRenderer = Minecraft.getInstance().font;
@@ -576,7 +577,7 @@ public abstract class ConfigGui
private final Component text;
private final List<AbstractWidget> children = new ArrayList<>();
public static final Map<AbstractWidget, Component> buttonsWithText = new HashMap<>();
private ButtonEntry(AbstractWidget button, Component text, AbstractWidget resetButton, AbstractWidget indexButton)
{
buttonsWithText.put(button, text);
@@ -591,12 +592,12 @@ public abstract class ConfigGui
if (indexButton != null)
children.add(indexButton);
}
public static ButtonEntry create(AbstractWidget button, Component text, AbstractWidget resetButton, AbstractWidget indexButton)
{
return new ButtonEntry(button, text, resetButton, indexButton);
}
@Override
public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta)
{
@@ -618,17 +619,19 @@ public abstract class ConfigGui
if (text != null && (!text.getString().contains("spacer") || button != null))
GuiComponent.drawString(matrices, textRenderer, text, 12, y + 5, 0xFFFFFF);
}
@Override
public List<? extends GuiEventListener> children()
{
return children;
}
@Override
public List<? extends NarratableEntry> narratables()
{
return children;
}
// Only for 1.17 and over
// Remove in 1.16 and below
@Override
public List<? extends NarratableEntry> narratables()
{
return children;
}
}
}
+3 -3
View File
@@ -6,9 +6,9 @@
"name": "Distant Horizons Fabric",
"description": "This mod generates and renders simplified terrain beyond the normal view distance at a low performance cost. Allowing you to see much farther without turning your game into a slideshow.",
"authors": [
"James Seibel",
"Leonardo Amato",
"Cola",
"James Seibel",
"Leonardo Amato",
"Cola",
"coolGi2007",
"Ran"
],