package eu.midnightdust.lib.config;
import com.google.common.collect.Lists;
import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder;
import com.mojang.blaze3d.systems.RenderSystem;
import eu.midnightdust.lib.util.PlatformFunctions;
import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.Element; import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.tab.GridScreenTab; import net.minecraft.client.gui.tab.Tab; import net.minecraft.client.gui.tab.TabManager;
import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.*;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.registry.Registries;
import net.minecraft.screen.ScreenTexts;
import net.minecraft.text.Style; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.Color;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType;
import java.nio.file.Files; import java.nio.file.Path;
import java.util.*;
import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate;
import java.util.regex.Pattern;
import static net.minecraft.client.MinecraftClient.IS_SYSTEM_MAC;
/** MidnightConfig v2.6.0 by Martin "Motschen" Prokoph
* Single class config library - feel free to copy!
* Based on ...
* Credits to Minenash */
@SuppressWarnings("unchecked")
public abstract class MidnightConfig {
private static final Pattern INTEGER_ONLY = Pattern.compile("(-?[0-9]*)");
private static final Pattern DECIMAL_ONLY = Pattern.compile("-?(\\d+\\.?\\d*|\\d*\\.?\\d+|\\.)");
private static final Pattern HEXADECIMAL_ONLY = Pattern.compile("(-?[#0-9a-fA-F]*)");
private static final List entries = new ArrayList<>();
public static class EntryInfo {
Field field;
Class> dataType;
int width, listIndex;
boolean centered;
Object defaultValue, value, function;
String modid, tempValue; // The value visible in the config screen
boolean inLimits = true;
Text name, error;
ClickableWidget actionButton; // color picker button / explorer button
Tab tab;
public void setValue(Object value) {
if (this.field.getType() != List.class) { this.value = value;
this.tempValue = value.toString();
} else { writeList(this.listIndex, value);
this.tempValue = toTemporaryValue(); }
}
public String toTemporaryValue() {
if (this.field.getType() != List.class) return this.value.toString();
else try { return ((List>) this.value).get(this.listIndex).toString(); } catch (Exception ignored) {return "";}
}
public void writeList(int index, T value) {
var list = (List) this.value;
if (index >= list.size()) list.add(value);
else list.set(index, value);
}
}
public static final Map> configClass = new HashMap<>();
private static Path path;
private static final Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT).excludeFieldsWithModifiers(Modifier.PRIVATE)
.addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy())
.registerTypeAdapter(Identifier.class, new Identifier.Serializer())
.setPrettyPrinting().create();
@SuppressWarnings("unused") // shhhhhh...
public static @Nullable Object getDefaultValue(String modid, String entry) {
for (EntryInfo e : entries) {
if (modid.equals(e.modid) && entry.equals(e.field.getName())) return e.defaultValue;
} return null;
}
public static void init(String modid, Class extends MidnightConfig> config) {
path = PlatformFunctions.getConfigDirectory().resolve(modid + ".json");
configClass.put(modid, config);
for (Field field : config.getFields()) {
EntryInfo info = new EntryInfo();
if ((field.isAnnotationPresent(Entry.class) || field.isAnnotationPresent(Comment.class)) && !field.isAnnotationPresent(Server.class) && !field.isAnnotationPresent(Hidden.class) && PlatformFunctions.isClientEnv())
initClient(modid, field, info);
if (field.isAnnotationPresent(Entry.class))
try { info.defaultValue = field.get(null);
} catch (IllegalAccessException ignored) {}
}
try { gson.fromJson(Files.newBufferedReader(path), config); }
catch (Exception e) { write(modid); }
for (EntryInfo info : entries) {
if (info.field.isAnnotationPresent(Entry.class)) try {
info.value = info.field.get(null);
info.tempValue = info.toTemporaryValue();
} catch (IllegalAccessException ignored) {}
}
}
@SuppressWarnings("ConstantValue") //pertains to requiredModLoaded
@Environment(EnvType.CLIENT)
private static void initClient(String modid, Field field, EntryInfo info) {
info.dataType = getUnderlyingType(field);
Entry e = field.getAnnotation(Entry.class);
Comment c = field.getAnnotation(Comment.class);
info.width = e != null ? e.width() : 0;
info.field = field; info.modid = modid;
boolean requiredModLoaded = true;
if (e != null) {
if (!e.requiredMod().isEmpty()) requiredModLoaded = PlatformFunctions.isModLoaded(e.requiredMod());
if (!requiredModLoaded) return;
if (!e.name().isEmpty()) info.name = Text.translatable(e.name());
if (info.dataType == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, (int) e.min(), (int) e.max(), true);
else if (info.dataType == float.class) textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false);
else if (info.dataType == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(), false);
else if (info.dataType == String.class || info.dataType == Identifier.class) textField(info, String::length, null, Math.min(e.min(), 0), Math.max(e.max(), 1), true);
else if (info.dataType == boolean.class) {
Function