[config] Remove EntryInfo data class

This commit is contained in:
Johannes Frohnmeyer 2022-04-29 15:48:27 +02:00
parent 10d62e5a61
commit b655c6ab67
Signed by: Johannes
GPG Key ID: E76429612C2929F4
41 changed files with 550 additions and 339 deletions

View File

@ -28,7 +28,8 @@ allprojects {
dependencies {
modImplementation("com.terraformersmc:modmenu:3.1.0")
modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:$project.fabric_version")
modImplementation('io.gitlab.jfronny:gson:2.9.0.2022.4.2.19.45.43')
modImplementation("io.gitlab.jfronny:commons:$project.commons_version")
modImplementation("io.gitlab.jfronny:commons-gson:$project.commons_version")
}
}

View File

@ -5,7 +5,7 @@ To add a config create a class using only static fields with default values like
```java
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import io.gitlab.jfronny.libjf.config.api.Entry;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
import io.gitlab.jfronny.commons.serialize.gson.GsonIgnore;
public class TestConfig implements JfConfig {
@Entry public static boolean disablePacks = false;
@ -14,7 +14,7 @@ public class TestConfig implements JfConfig {
@Entry(min = -6) public static float floatTest = -5;
@Entry(max = 21) public static double doubleTest = 20;
@Entry public static String dieStr = "lolz";
@Entry @GsonHidden public static String guiOnlyStr = "lolz";
@Entry @GsonIgnore public static String guiOnlyStr = "lolz";
public static String gsonOnlyStr = "lolz";
@Entry public static Test enumTest = Test.Test;

View File

@ -11,4 +11,6 @@ dev_only_module=libjf-devutil-v0
modrinth_id=WKwQAwke
modrinth_optional_dependencies=P7dR8mSH
curseforge_id=482600
curseforge_optional_dependencies=fabric-api
curseforge_optional_dependencies=fabric-api
commons_version=0.1.0.2022.4.29.11.8.31

View File

@ -1,6 +1,7 @@
archivesBaseName = "libjf-base"
dependencies {
include modImplementation(fabricApi.module("fabric-lifecycle-events-v1", "${rootProject.fabric_version}"))
shadow 'io.gitlab.jfronny:gson:2.9.0.2022.4.2.19.45.43'
include modImplementation(fabricApi.module("fabric-lifecycle-events-v1", "$rootProject.fabric_version"))
shadow "io.gitlab.jfronny:commons:$rootProject.commons_version"
shadow "io.gitlab.jfronny:commons-gson:$rootProject.commons_version"
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.libjf;
import io.gitlab.jfronny.commons.serialize.gson.GsonHolder;
import io.gitlab.jfronny.gson.Gson;
import io.gitlab.jfronny.gson.GsonBuilder;
import io.gitlab.jfronny.libjf.gson.HiddenAnnotationExclusionStrategy;
@ -15,14 +16,19 @@ public class LibJf {
}
public static final String MOD_ID = "libjf";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
public static final Gson GSON = FabricLoader.getInstance()
.getEntrypoints(MOD_ID + ":gson_adapter", GsonAdapter.class)
.stream()
.reduce(adapter -> adapter, (left, right) -> adapter -> left.apply(right.apply(adapter)))
.apply(new GsonBuilder())
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy())
.setPrettyPrinting()
.create();
@Deprecated(forRemoval = true)
public static final Gson GSON;
static {
GsonHolder.modifyBuilder((final GsonBuilder builder) -> {
for (GsonAdapter adapter : FabricLoader.getInstance().getEntrypoints(MOD_ID + ":gson_adapter", GsonAdapter.class)) {
if (adapter.apply(builder) != builder) {
LOGGER.warn("gson_adapter attempted to replace GsonBuilder. This is no longer supported");
}
}
});
HiddenAnnotationExclusionStrategy.register();
GSON = GsonHolder.getGson();
GsonHolder.register();
}
}

View File

@ -6,6 +6,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import java.util.function.Supplier;
@Deprecated(forRemoval = true)
public class LazySupplier<T> implements Supplier<T> {
private final Supplier<T> supplier;
private T cache = null;

View File

@ -4,6 +4,7 @@ import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@Deprecated(forRemoval = true)
@FunctionalInterface
public interface ThrowingBiConsumer<T, U, TEx extends Throwable> {
void accept(T var1, U var2) throws TEx;

View File

@ -4,6 +4,7 @@ import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
@Deprecated(forRemoval = true)
@FunctionalInterface
public interface ThrowingBiFunction<T, U, R, TEx extends Throwable> {
R apply(T var1, U var2) throws TEx;

View File

@ -4,6 +4,7 @@ import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;
@Deprecated(forRemoval = true)
@FunctionalInterface
public interface ThrowingBooleanSupplier<TEx extends Throwable> {
boolean get() throws TEx;

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.libjf.generic;
import java.util.Objects;
import java.util.function.Consumer;
@Deprecated(forRemoval = true)
@FunctionalInterface
public interface ThrowingConsumer<T, TEx extends Throwable> {
void accept(T var1) throws TEx;

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.libjf.generic;
import java.util.Objects;
import java.util.function.Function;
@Deprecated(forRemoval = true)
@FunctionalInterface
public interface ThrowingFunction<T, R, TEx extends Throwable> {
R apply(T var1) throws TEx;

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.libjf.generic;
import java.util.Objects;
import java.util.function.Predicate;
@Deprecated(forRemoval = true)
@FunctionalInterface
public interface ThrowingPredicate<T, TEx extends Throwable> {
boolean test(T var1) throws TEx;

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.libjf.generic;
import java.util.Objects;
import java.util.function.Consumer;
@Deprecated(forRemoval = true)
@FunctionalInterface
public interface ThrowingRunnable<TEx extends Throwable> {
void run() throws TEx;

View File

@ -4,6 +4,7 @@ import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
@Deprecated(forRemoval = true)
@FunctionalInterface
public interface ThrowingSupplier<T, TEx extends Throwable> {
T get() throws TEx;

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.libjf.generic;
import java.util.Objects;
import java.util.function.*;
@Deprecated(forRemoval = true)
public class Try {
public static void orElse(ThrowingRunnable<?> tr, Consumer<Throwable> alternative) {
Objects.requireNonNull(tr).addHandler(alternative).run();

View File

@ -7,5 +7,6 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Deprecated(forRemoval = true)
public @interface GsonHidden {
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.libjf.gson;
import io.gitlab.jfronny.commons.serialize.gson.GsonHolder;
import io.gitlab.jfronny.gson.ExclusionStrategy;
import io.gitlab.jfronny.gson.FieldAttributes;
import net.fabricmc.api.EnvType;
@ -15,4 +16,8 @@ public class HiddenAnnotationExclusionStrategy implements ExclusionStrategy {
? fieldAttributes.getAnnotation(ServerOnly.class) != null
: fieldAttributes.getAnnotation(ClientOnly.class) != null;
}
public static void register() {
GsonHolder.modifyBuilder(builder -> builder.setExclusionStrategies(new HiddenAnnotationExclusionStrategy()));
}
}

View File

@ -2,7 +2,6 @@ package io.gitlab.jfronny.libjf.config.api;
import io.gitlab.jfronny.gson.JsonObject;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.config.impl.EntryInfo;
import org.jetbrains.annotations.ApiStatus;
import java.io.IOException;
@ -19,13 +18,12 @@ public interface ConfigInstance {
void load();
void write();
String getModId();
List<EntryInfo> getEntries();
List<EntryInfo<?>> getEntries();
Map<String, Runnable> getPresets();
List<String> getReferencedConfigs();
Map<String, ConfigInstance> getCategories();
@ApiStatus.Internal void syncToClass();
@ApiStatus.Internal void syncFromClass();
@ApiStatus.Internal void verify();
@ApiStatus.Internal boolean matchesConfigClass(Class<?> candidate);
@ApiStatus.Internal void loadFrom(JsonObject source);
@ApiStatus.Internal void writeTo(JsonWriter writer) throws IOException;

View File

@ -0,0 +1,85 @@
package io.gitlab.jfronny.libjf.config.api;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.config.impl.EntryInfoImpl;
import net.minecraft.client.gui.widget.ClickableWidget;
import java.io.IOException;
import java.lang.reflect.Field;
//TODO isolate UI stuff from this
public interface EntryInfo<T> extends WidgetFactory {
static <T> EntryInfo<T> fromField(Field field) {
EntryInfoImpl<T> info = new EntryInfoImpl<>();
info.field = field;
if (field.isAnnotationPresent(Entry.class)) {
info.entry = field.getAnnotation(Entry.class);
try {
info.defaultValue = (T) field.get(null);
} catch (IllegalAccessException ignored) {}
}
return info;
}
/**
* @return Get the default value of this entry
*/
T getDefault();
/**
* Gets the current value
* @return The current value
*/
T getValue() throws IllegalAccessException;
/**
* Set the current value to the parameter
* @param value The value to use
*/
void setValue(T value) throws IllegalAccessException;
/**
* Get the value type of this entry. Will use the class definition, not the current value
* @return The type of this entry
*/
Class<T> getValueType();
/**
* Ensure the current value is within expected bounds.
*/
void fix();
/**
* Get the name of this entry
* @return This entry's name
*/
String getName();
/**
* Set this entry's value to that of the element
* @param element The element to read from
*/
void loadFromJson(JsonElement element) throws IllegalAccessException;
/**
* Write the currently cached value to the writer
* @param writer The writer to write to
*/
void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException;
/**
* Get metadata associated with this entry using the Entry class
* @return The annotation instance
*/
Entry getEntryMeta();
/**
* Configure a widget factory for this entry
* @param factory The factory to use
*/
void setWidgetFactory(WidgetFactory factory);
record Widget(Runnable update, ClickableWidget widget) {
}
}

View File

@ -0,0 +1,8 @@
package io.gitlab.jfronny.libjf.config.api;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.widget.ButtonWidget;
public interface WidgetFactory {
EntryInfo.Widget build(int screenWidth, TextRenderer textRenderer, ButtonWidget done);
}

View File

@ -1,6 +1,8 @@
package io.gitlab.jfronny.libjf.config.impl;
import com.google.common.collect.ImmutableMap;
import io.gitlab.jfronny.commons.serialize.SerializerHolder;
import io.gitlab.jfronny.commons.serialize.gson.GsonHolder;
import io.gitlab.jfronny.gson.Gson;
import io.gitlab.jfronny.gson.GsonBuilder;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
@ -25,12 +27,6 @@ public class ConfigHolderImpl implements ConfigHolder {
private ConfigHolderImpl() {
}
public static final String MODULE_ID = "libjf:config";
public static Gson GSON = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy())
.setPrettyPrinting()
.create();
private final Map<String, ConfigInstance> configs = new HashMap<>();
private final Map<Path, ConfigInstance> configsByPath = new HashMap<>();
@ -56,7 +52,7 @@ public class ConfigHolderImpl implements ConfigHolder {
cv = cv.getAsObject().get("config");
}
}
if (cv != null) meta = GSON.fromJson(FabricLoaderGsonGenerator.toGson(cv), AuxiliaryMetadata.class);
if (cv != null) meta = GsonHolder.getGson().fromJson(FabricLoaderGsonGenerator.toGson(cv), AuxiliaryMetadata.class);
}
else {
SafeLog.warn("Attempted to register config for a mod that is not installed: " + modId);

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.commons.throwable.Coerce;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.JsonObject;
import io.gitlab.jfronny.gson.stream.JsonWriter;
@ -12,7 +13,6 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
public abstract class ConfigInstanceAbstract implements ConfigInstance {
public static final String CONFIG_PRESET_DEFAULT = "libjf-config-v0.default";
@ -20,7 +20,7 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
private final String categoryPath;
public final Class<?> configClass;
public final List<String> referencedConfigs;
public final List<EntryInfo> entries = new ArrayList<>();
public final List<EntryInfo<?>> entries = new ArrayList<>();
public final Map<String, Runnable> presets = new LinkedHashMap<>();
public final Set<Runnable> verifiers = new LinkedHashSet<>();
public final Map<String, ConfigInstance> subcategories = new LinkedHashMap<>();
@ -30,20 +30,12 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
this.configClass = configClass;
this.referencedConfigs = List.copyOf(meta.referencedConfigs);
for (Field field : configClass.getFields()) {
EntryInfo info = new EntryInfo();
info.field = field;
if (field.isAnnotationPresent(Entry.class)) {
info.entry = field.getAnnotation(Entry.class);
try {
info.defaultValue = field.get(null);
} catch (IllegalAccessException ignored) {}
}
entries.add(info);
entries.add(EntryInfo.fromField(field));
}
presets.put(CONFIG_PRESET_DEFAULT, () -> {
for (EntryInfo entry : entries) {
for (EntryInfo<?> entry : entries) {
try {
entry.field.set(null, entry.defaultValue);
reset(entry);
} catch (IllegalAccessException e) {
SafeLog.error("Could not reload default values", e);
}
@ -71,32 +63,8 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
}
}
verifiers.add(() -> {
for (EntryInfo entry : entries) {
Object value;
try {
value = entry.field.get(null);
} catch (IllegalAccessException e) {
SafeLog.error("Could not read value", e);
continue;
}
final Object valueOriginal = value;
if (value instanceof final Integer v) {
if (v < entry.entry.min()) value = (int)entry.entry.min();
if (v > entry.entry.max()) value = (int)entry.entry.max();
} else if (value instanceof final Float v) {
if (v < entry.entry.min()) value = (float)entry.entry.min();
if (v > entry.entry.max()) value = (float)entry.entry.max();
} else if (value instanceof final Double v) {
if (v < entry.entry.min()) value = entry.entry.min();
if (v > entry.entry.max()) value = entry.entry.max();
}
if (valueOriginal != value) {
try {
entry.field.set(null, value);
} catch (IllegalAccessException e) {
SafeLog.error("Could not write value", e);
}
}
for (EntryInfo<?> entry : entries) {
entry.fix();
}
});
@ -109,6 +77,11 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
}
}
// Here due to generics issues
private <T> void reset(EntryInfo<T> info) throws IllegalAccessException {
info.setValue(info.getDefault());
}
private String camelCase(String source) {
if (source == null) return null;
if (source.length() == 0) return source;
@ -117,11 +90,14 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
@Override
public void loadFrom(JsonObject source) {
for (EntryInfo entry : entries) {
if (source.has(entry.field.getName())) {
JsonElement el = source.get(entry.field.getName());
entry.value = ConfigHolderImpl.GSON.fromJson(el, entry.field.getGenericType());
} else SafeLog.error("Config does not contain entry for " + entry.field.getName());
for (EntryInfo<?> entry : entries) {
if (source.has(entry.getName())) {
try {
entry.loadFromJson(source.get(entry.getName()));
} catch (IllegalAccessException e) {
SafeLog.error("Could not set config entry value of " + entry.getName(), e);
}
} else SafeLog.error("Config does not contain entry for " + entry.getName());
}
for (Map.Entry<String, ConfigInstance> entry : subcategories.entrySet()) {
if (source.has(entry.getKey())) {
@ -130,21 +106,19 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
else SafeLog.error("Config category is not a JSON object, skipping");
} else SafeLog.error("Config does not contain entry for subcategory " + entry.getKey());
}
syncToClass();
}
@Override
public void writeTo(JsonWriter writer) throws IOException {
syncFromClass();
verify();
writer.beginObject();
String val;
for (EntryInfo entry : entries) {
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(modId + ".jfconfig." + categoryPath + entry.field.getName() + ".tooltip")) != null)
writer.comment(val);
if (entry.field.getType().isEnum())
writer.comment("Valid: [" + Arrays.stream(entry.field.getType().getEnumConstants()).map(Objects::toString).collect(Collectors.joining(", ")) + "]");
writer.name(entry.field.getName());
ConfigHolderImpl.GSON.toJson(entry.value, entry.field.getGenericType(), writer);
for (EntryInfo<?> entry : entries) {
try {
entry.writeTo(writer, modId + ".jfconfig." + categoryPath);
} catch (IllegalAccessException e) {
SafeLog.error("Could not write entry", e);
}
}
for (Map.Entry<String, ConfigInstance> entry : subcategories.entrySet()) {
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(modId + ".jfconfig." + categoryPath + entry.getKey() + ".title")) != null)
@ -156,31 +130,8 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
}
@Override
public void syncToClass() {
for (EntryInfo info : entries) {
try {
info.field.set(null, info.value);
} catch (IllegalAccessException e) {
SafeLog.error("Could not write value", e);
}
}
syncFromClass();
}
@Override
public void syncFromClass() {
for (Runnable verifier : verifiers) {
verifier.run();
}
for (EntryInfo info : entries) {
try {
info.value = info.field.get(null);
if (info.value == null) info.value = info.defaultValue;
info.tempValue = info.value == null ? null : info.value.toString();
} catch (IllegalAccessException e) {
SafeLog.error("Could not read value", e);
}
}
public void verify() {
for (Runnable verifier : verifiers) verifier.run();
}
@Override
@ -199,7 +150,7 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
}
@Override
public List<EntryInfo> getEntries() {
public List<EntryInfo<?>> getEntries() {
return entries;
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.commons.serialize.gson.GsonHolder;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.JsonParser;
import io.gitlab.jfronny.gson.stream.JsonWriter;
@ -41,7 +42,7 @@ public class ConfigInstanceRoot extends ConfigInstanceAbstract {
public void write() {
JfConfigWatchService.lock(path, () -> {
try (BufferedWriter bw = Files.newBufferedWriter(path);
JsonWriter jw = ConfigHolderImpl.GSON.newJsonWriter(bw)) {
JsonWriter jw = GsonHolder.getGson().newJsonWriter(bw)) {
writeTo(jw);
} catch (Exception e) {
SafeLog.error("Could not write config", e);

View File

@ -1,30 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.libjf.config.api.Entry;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;
import java.lang.reflect.Field;
import java.util.Map;
public class EntryInfo {
public Field field;
public WidgetFactory widget;
public int width;
public Map.Entry<TextFieldWidget, Text> error;
public Object defaultValue;
public Object value;
public String tempValue;
public boolean inLimits = true;
public Entry entry;
public interface WidgetFactory {
Widget build(int width, TextRenderer textRenderer, ButtonWidget done);
}
public record Widget(Runnable update, ClickableWidget widget) {
}
}

View File

@ -0,0 +1,114 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.commons.serialize.gson.GsonHolder;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.config.api.Entry;
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.WidgetFactory;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.widget.ButtonWidget;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;
public class EntryInfoImpl<T> implements EntryInfo<T> {
public Field field;
public WidgetFactory widget;
public T defaultValue;
public Entry entry;
public EntryInfoImpl() {
}
@Override
public T getDefault() {
return defaultValue;
}
@Override
public T getValue() throws IllegalAccessException {
return (T) field.get(null);
}
@Override
public void setValue(T value) throws IllegalAccessException {
field.set(null, value);
}
@Override
public Class<T> getValueType() {
return (Class<T>) field.getType();
}
@Override
public void fix() {
Object value;
try {
value = field.get(null);
} catch (IllegalAccessException e) {
SafeLog.error("Could not read value", e);
return;
}
final Object valueOriginal = value;
if (value instanceof final Integer v) {
if (v < entry.min()) value = (int)entry.min();
if (v > entry.max()) value = (int)entry.max();
} else if (value instanceof final Float v) {
if (v < entry.min()) value = (float)entry.min();
if (v > entry.max()) value = (float)entry.max();
} else if (value instanceof final Double v) {
if (v < entry.min()) value = entry.min();
if (v > entry.max()) value = entry.max();
}
if (valueOriginal != value) {
try {
field.set(null, value);
} catch (IllegalAccessException e) {
SafeLog.error("Could not write value", e);
}
}
}
@Override
public String getName() {
return field.getName();
}
@Override
public void loadFromJson(JsonElement element) throws IllegalAccessException {
setValue(GsonHolder.getGson().fromJson(element, field.getGenericType()));
}
@Override
public void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException {
T value = getValue();
String val;
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(translationPrefix + getName() + ".tooltip")) != null)
writer.comment(val);
if (field.getType().isEnum())
writer.comment("Valid: [" + Arrays.stream(field.getType().getEnumConstants()).map(Objects::toString).collect(Collectors.joining(", ")) + "]");
writer.name(getName());
GsonHolder.getGson().toJson(value, field.getGenericType(), writer);
}
@Override
public Entry getEntryMeta() {
return entry;
}
@Override
public void setWidgetFactory(WidgetFactory factory) {
this.widget = factory;
}
@Override
public Widget build(int screenWidth, TextRenderer textRenderer, ButtonWidget done) {
return widget.build(screenWidth, textRenderer, done);
}
}

View File

@ -3,9 +3,17 @@ package io.gitlab.jfronny.libjf.config.impl;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import io.gitlab.jfronny.commons.throwable.Coerce;
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import io.gitlab.jfronny.commons.throwable.Try;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.Entry;
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.server.command.ServerCommandSource;
@ -57,32 +65,8 @@ public class JfConfigCommand implements ModInitializer {
context.getSource().sendFeedback(new LiteralText("[libjf-config-v0] " + subpath + " is a category"), false);
return Command.SINGLE_SUCCESS;
});
for (EntryInfo entry : config.getEntries()) {
LiteralArgumentBuilder<ServerCommandSource> c_entry = literal(entry.field.getName()).executes(context -> {
context.getSource().sendFeedback(new LiteralText("[libjf-config-v0] The value of " + subpath + entry.field.getName() + " is " + entry.value), false);
return Command.SINGLE_SUCCESS;
});
ArgumentType<?> type = getType(entry);
if (type != null) {
c_entry.then(argument("value", type).executes(context -> {
Object value = context.getArgument("value", entry.field.getType());
entry.value = value;
config.syncToClass();
context.getSource().sendFeedback(new LiteralText("[libjf-config-v0] Set " + subpath + entry.field.getName() + " to " + value), true);
return Command.SINGLE_SUCCESS;
}));
}
else if (entry.field.getType().isEnum()) {
for (Object enumConstant : entry.field.getType().getEnumConstants()) {
c_entry.then(literal(enumConstant.toString()).executes(context -> {
entry.value = enumConstant;
config.syncToClass();
context.getSource().sendFeedback(new LiteralText("[libjf-config-v0] Set " + subpath + entry.field.getName() + " to " + enumConstant), true);
return Command.SINGLE_SUCCESS;
}));
}
}
cns.then(c_entry);
for (EntryInfo<?> entry : config.getEntries()) {
registerEntry(config, subpath, cns, entry);
}
}));
c_reload.then(pathGen.apply(cns -> cns.executes(context -> {
@ -115,13 +99,54 @@ public class JfConfigCommand implements ModInitializer {
});
}
private ArgumentType<?> getType(EntryInfo info) {
Class<?> type = info.field.getType();
if (type == int.class || type == Integer.class) return IntegerArgumentType.integer((int) info.entry.min(), (int) info.entry.max());
else if (type == float.class || type == Float.class) return FloatArgumentType.floatArg((float) info.entry.min(), (float) info.entry.max());
else if (type == double.class || type == Double.class) return DoubleArgumentType.doubleArg(info.entry.min(), info.entry.max());
private final DynamicCommandExceptionType eType = new DynamicCommandExceptionType(o -> {
if (o instanceof Throwable throwable) {
return new LiteralText("Could not execute command: " + throwable.getMessage());
} else return new LiteralText("Could not execute command");
});
private <T> void registerEntry(ConfigInstance config, String subpath, LiteralArgumentBuilder<ServerCommandSource> cns, EntryInfo<T> entry) {
LiteralArgumentBuilder<ServerCommandSource> c_entry = literal(entry.getName()).executes(context -> {
context.getSource().sendFeedback(new LiteralText("[libjf-config-v0] The value of " + subpath + entry.getName() + " is " + tryRun(entry::getValue)), false);
return Command.SINGLE_SUCCESS;
});
ArgumentType<?> type = getType(entry);
if (type != null) {
c_entry.then(argument("value", type).executes(context -> {
T value = context.getArgument("value", entry.getValueType());
tryRun(() -> entry.setValue(value));
context.getSource().sendFeedback(new LiteralText("[libjf-config-v0] Set " + subpath + entry.getName() + " to " + value), true);
return Command.SINGLE_SUCCESS;
}));
}
else if (entry.getValueType().isEnum()) {
for (T enumConstant : entry.getValueType().getEnumConstants()) {
c_entry.then(literal(enumConstant.toString()).executes(context -> {
tryRun(() -> entry.setValue(enumConstant));
context.getSource().sendFeedback(new LiteralText("[libjf-config-v0] Set " + subpath + entry.getName() + " to " + enumConstant), true);
return Command.SINGLE_SUCCESS;
}));
}
}
cns.then(c_entry);
}
private <T> ArgumentType<?> getType(EntryInfo<T> info) {
Class<T> type = info.getValueType();
Entry e = info.getEntryMeta();
if (type == int.class || type == Integer.class) return IntegerArgumentType.integer((int) e.min(), (int) e.max());
else if (type == float.class || type == Float.class) return FloatArgumentType.floatArg((float) e.min(), (float) e.max());
else if (type == double.class || type == Double.class) return DoubleArgumentType.doubleArg(e.min(), e.max());
else if (type == String.class) return StringArgumentType.greedyString();
else if (type == boolean.class || type == Boolean.class) return BoolArgumentType.bool();
else return null;
}
private <T> T tryRun(ThrowingSupplier<T, ?> supplier) throws CommandSyntaxException {
return supplier.orThrow(eType::create).get();
}
private void tryRun(ThrowingRunnable<?> supplier) throws CommandSyntaxException {
supplier.orThrow(eType::create).run();
}
}

View File

@ -3,7 +3,7 @@ package io.gitlab.jfronny.libjf.config.impl.client;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.impl.client.screen.EntryInfoWidgetBuilder;
import io.gitlab.jfronny.libjf.config.impl.client.gui.EntryInfoWidgetBuilder;
import net.fabricmc.api.ClientModInitializer;
public class JfConfigClient implements ClientModInitializer {
@ -11,7 +11,6 @@ public class JfConfigClient implements ClientModInitializer {
public void onInitializeClient() {
for (ConfigInstance config : ConfigHolder.getInstance().getRegistered().values()) {
LibJf.LOGGER.info("Registering config UI for " + config.getModId());
EntryInfoWidgetBuilder.initConfig(config);
}
}
}

View File

@ -5,7 +5,7 @@ import com.terraformersmc.modmenu.api.ModMenuApi;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.client.screen.TinyConfigScreen;
import io.gitlab.jfronny.libjf.config.impl.client.gui.TinyConfigScreen;
import java.util.HashMap;
import java.util.Map;

View File

@ -0,0 +1,133 @@
package io.gitlab.jfronny.libjf.config.impl.client.gui;
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;
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) {
if (info.getEntryMeta() == null)
return null;
Class<T> type = info.getValueType();
Entry meta = info.getEntryMeta();
WidgetState<T> state = new WidgetState<>();
state.entry = info;
state.knownStates = knownStates;
if (type == int.class || type == Integer.class) textField(info, state, INTEGER_ONLY, Integer::parseInt, true, meta.min(), meta.max());
else if (type == float.class || type == Float.class) textField(info, state, DECIMAL_ONLY, Float::parseFloat, false, meta.min(), meta.max());
else if (type == double.class || type == Double.class) textField(info, state, DECIMAL_ONLY, Double::parseDouble, false, meta.min(), meta.max());
else if (type == String.class) textField(info, state, null, String::length, true, Math.min(meta.min(),0), Math.max(meta.max(),1));
else if (type == boolean.class || type == Boolean.class) {
toggle(info, state,
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());
toggle(info, state, value -> {
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());
info.setWidgetFactory(((screenWidth, textRenderer, done) -> new EntryInfo.Widget(() -> {}, new ButtonWidget(-10, 0, 0, 0, Text.of(""), null))));
}
try {
state.updateCache(info.getValue());
} catch (IllegalAccessException ignored) {
}
return state;
}
private static <T> void toggle(EntryInfo<T> info, WidgetState<T> state, Function<Object, Object> increment, Function<T, Text> valueTextifier) {
info.setWidgetFactory((screenWidth, textRenderer, done) -> {
ButtonWidget button = new ButtonWidget(screenWidth - 110, 0, info.getEntryMeta().width(), 20, valueTextifier.apply(state.cachedValue), btn -> {
state.updateCache((T) increment.apply(state.cachedValue));
btn.setMessage(valueTextifier.apply(state.cachedValue));
});
return new EntryInfoImpl.Widget(() -> button.setMessage(valueTextifier.apply(state.cachedValue)), button);
});
}
/**
* @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
*/
private static <T> void textField(EntryInfo<T> info, WidgetState<T> state, Pattern pattern, Function<String, Number> sizeFetcher, boolean wholeNumber, double min, double max) {
boolean isNumber = pattern != null;
info.setWidgetFactory((width, textRenderer, done) -> {
TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 110, 0, info.getEntryMeta().width(), 20, null);
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;
});
return new EntryInfoImpl.Widget(() -> widget.setText(state.cachedValue == null ? "" : state.cachedValue.toString()), widget);
});
}
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen;
package io.gitlab.jfronny.libjf.config.impl.client.gui;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;

View File

@ -1,9 +1,13 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen;
package io.gitlab.jfronny.libjf.config.impl.client.gui;
import io.gitlab.jfronny.commons.throwable.Coerce;
import io.gitlab.jfronny.commons.throwable.Try;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.impl.EntryInfo;
import io.gitlab.jfronny.libjf.config.impl.client.screen.presets.PresetsScreen;
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
import io.gitlab.jfronny.libjf.config.impl.EntryInfoImpl;
import io.gitlab.jfronny.libjf.config.impl.client.gui.presets.PresetsScreen;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
@ -24,24 +28,20 @@ public class TinyConfigScreen extends Screen {
super(new TranslatableText(config.getModId() + ".jfconfig." + config.getCategoryPath() + "title"));
this.parent = parent;
this.config = config;
this.widgets = EntryInfoWidgetBuilder.buildWidgets(config);
this.translationPrefix = config.getModId() + ".jfconfig." + config.getCategoryPath();
}
private final String translationPrefix;
private final Screen parent;
private final ConfigInstance config;
private final List<WidgetState<?>> widgets;
private MidnightConfigListWidget list;
// Real Time config update //
@Override
public void tick() {
config.syncToClass();
}
@Override
protected void init() {
super.init();
config.syncFromClass();
config.verify();
this.addDrawableChild(new ButtonWidget(4, 6, 80, 20, new TranslatableText("libjf-config-v0.presets"), button -> {
MinecraftClient.getInstance().setScreen(new PresetsScreen(this, config));
@ -53,7 +53,9 @@ public class TinyConfigScreen extends Screen {
}));
ButtonWidget done = this.addDrawableChild(new ButtonWidget(this.width / 2 + 4, this.height - 28, 150, 20, ScreenTexts.DONE, (button) -> {
config.syncToClass();
for (WidgetState<?> state : widgets) {
Try.orElse(state::writeToEntry, e -> SafeLog.error("Could not write config data to class", e));
}
config.write();
Objects.requireNonNull(client).setScreen(parent);
}));
@ -65,23 +67,18 @@ public class TinyConfigScreen extends Screen {
new TranslatableText(entry.getValue().getModId() + ".jfconfig." + entry.getValue().getCategoryPath() + "title"),
() -> new TinyConfigScreen(entry.getValue(), this));
}
for (EntryInfo info : config.getEntries()) {
TranslatableText name = new TranslatableText(translationPrefix + info.field.getName());
if (info.widget != null) {
EntryInfo.Widget control = info.widget.build(width, textRenderer, done);
ButtonWidget resetButton = new ButtonWidget(width - 155, 0, 40, 20, new TranslatableText("libjf-config-v0.reset"), (button -> {
info.value = info.defaultValue;
control.update().run();
}));
this.list.addButton(control.widget(), resetButton, () -> {
boolean visible = !Objects.equals(info.defaultValue, info.value);
resetButton.active = visible;
return visible;
}, name);
} else {
ButtonWidget dummy = new ButtonWidget(-10, 0, 0, 0, Text.of(""), null);
this.list.addButton(dummy,dummy, () -> false,name);
}
for (WidgetState<?> info : widgets) {
TranslatableText name = new TranslatableText(translationPrefix + info.entry.getName());
EntryInfoImpl.Widget control = info.entry.build(width, textRenderer, done);
ButtonWidget resetButton = new ButtonWidget(width - 155, 0, 40, 20, new TranslatableText("libjf-config-v0.reset"), (button -> {
info.reset();
control.update().run();
}));
this.list.addButton(control.widget(), resetButton, () -> {
boolean visible = !Objects.equals(info.entry.getDefault(), info.cachedValue);
resetButton.active = visible;
return visible;
}, name);
}
for (String referencedConfig : config.getReferencedConfigs()) {
ConfigInstance ci = ConfigHolder.getInstance().get(referencedConfig);
@ -102,14 +99,14 @@ public class TinyConfigScreen extends Screen {
Optional<Text> hovered = list.getHoveredEntryTitle(mouseY);
if (hovered.isPresent()) {
for (EntryInfo info : config.getEntries()) {
for (WidgetState<?> info : widgets) {
Text text = hovered.get();
TranslatableText name = new TranslatableText(this.translationPrefix + info.field.getName());
TranslatableText name = new TranslatableText(this.translationPrefix + info.entry.getName());
boolean showTooltip = text.equals(name);
String tooltipKey = translationPrefix + info.field.getName() + ".tooltip";
String tooltipKey = translationPrefix + info.entry.getName() + ".tooltip";
if (showTooltip && info.error != null) {
showTooltip = false;
renderTooltip(matrices, info.error.getValue(), mouseX, mouseY);
renderTooltip(matrices, info.error.right(), mouseX, mouseY);
}
if (showTooltip && I18n.hasTranslation(tooltipKey)) {
showTooltip = false;

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.libjf.config.impl.client.gui;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;
import java.util.List;
public class WidgetState<T> { //TODO convert this to an API class as a representation of a widget (instead of EntryInfo.Widget/WidgetFactory)
public Tuple<TextFieldWidget, Text> error;
public boolean inLimits = true;
public String tempValue;
public T cachedValue;
public EntryInfo<T> entry;
public List<WidgetState<?>> knownStates;
public void updateCache(T newValue) {
cachedValue = newValue;
tempValue = newValue == null ? null : newValue.toString();
}
public void writeToEntry() throws IllegalAccessException {
entry.setValue(cachedValue);
}
public void reset() {
cachedValue = entry.getDefault();
}
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen.presets;
package io.gitlab.jfronny.libjf.config.impl.client.gui.presets;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Element;

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen.presets;
package io.gitlab.jfronny.libjf.config.impl.client.gui.presets;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
@ -34,7 +34,7 @@ public class PresetsScreen extends Screen {
button -> {
LibJf.LOGGER.info("Preset selected: " + entry.getKey());
entry.getValue().run();
config.syncFromClass();
config.verify();
MinecraftClient.getInstance().setScreen(parent);
}));
}

View File

@ -1,127 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen;
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.impl.EntryInfo;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
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.AbstractMap;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@Environment(EnvType.CLIENT)
public class EntryInfoWidgetBuilder {
private static final Pattern INTEGER_ONLY = Pattern.compile("(-?[0-9]*)");
private static final Pattern DECIMAL_ONLY = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)");
public static void initConfig(ConfigInstance config) {
for (EntryInfo info : config.getEntries()) {
if (info.field.isAnnotationPresent(Entry.class) || info.field.isAnnotationPresent(GsonHidden.class))
try {
initEntry(config, info);
} catch (Exception ignored) {
}
}
for (ConfigInstance value : config.getCategories().values()) {
initConfig(value);
}
}
private static void initEntry(ConfigInstance config, EntryInfo info) {
if (!(info.field.isAnnotationPresent(io.gitlab.jfronny.libjf.config.api.Entry.class) || info.field.isAnnotationPresent(GsonHidden.class))) return;
Class<?> type = info.field.getType();
info.width = info.entry != null ? info.entry.width() : 0;
if (info.entry == null) return;
if (type == int.class || type == Integer.class) textField(config, info, INTEGER_ONLY, Integer::parseInt, true, info.entry.min(), info.entry.max());
else if (type == float.class || type == Float.class) textField(config, info, DECIMAL_ONLY, Float::parseFloat, false, info.entry.min(), info.entry.max());
else if (type == double.class || type == Double.class) textField(config, info, DECIMAL_ONLY, Double::parseDouble, false, info.entry.min(), info.entry.max());
else if (type == String.class) textField(config, info, null, String::length, true, Math.min(info.entry.min(),0), Math.max(info.entry.max(),1));
else if (type == boolean.class || type == Boolean.class) {
toggle(info,
() -> info.value = !(Boolean) info.value,
value -> new LiteralText((Boolean) value ? "True" : "False").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED));
} else if (type.isEnum()) {
List<?> values = Arrays.asList(info.field.getType().getEnumConstants());
toggle(info, () -> {
int index = values.indexOf(info.value) + 1;
info.value = values.get(index >= values.size() ? 0 : index);
}, value -> new TranslatableText(config.getModId() + ".jfconfig.enum." + type.getSimpleName() + "." + info.value.toString()));
} else LibJf.LOGGER.error("Invalid entry type in " + info.field.getName() + ": " + type.getName());
try {
info.value = info.field.get(null);
info.tempValue = info.value.toString();
} catch (IllegalAccessException ignored) {
}
}
private static void toggle(EntryInfo info, Runnable increment, Function<Object, Text> valueTextifier) {
info.widget = (width, textRenderer, done) -> {
ButtonWidget button = new ButtonWidget(width - 110, 0, info.width, 20, valueTextifier.apply(info.value), btn -> {
increment.run();
btn.setMessage(valueTextifier.apply(info.value));
});
return new EntryInfo.Widget(() -> button.setMessage(valueTextifier.apply(info.value)), button);
};
}
/**
* @param config The config this entry is a part of
* @param info The entry to generate a widget for
* @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
*/
private static void textField(ConfigInstance config, EntryInfo info, Pattern pattern, Function<String, Number> sizeFetcher, boolean wholeNumber, double min, double max) {
boolean isNumber = pattern != null;
info.widget = (width, textRenderer, done) -> {
TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 110, 0, info.width, 20, null);
widget.setText(info.tempValue);
Predicate<String> processor = s -> {
s = s.trim();
if (!(s.isEmpty() || !isNumber || pattern.matcher(s).matches())) return false;
Number value = 0;
boolean inLimits = false;
info.error = null;
if (!(isNumber && s.isEmpty()) && !s.equals("-") && !s.equals(".")) {
value = sizeFetcher.apply(s);
inLimits = value.doubleValue() >= min && value.doubleValue() <= max;
info.error = inLimits? null : new AbstractMap.SimpleEntry<>(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)));
}
info.tempValue = s;
widget.setEditableColor(inLimits? 0xFFFFFFFF : 0xFFFF7777);
info.inLimits = inLimits;
done.active = config.getEntries().stream().allMatch(e -> e.inLimits);
if (inLimits)
info.value = isNumber? value : s;
return true;
};
widget.setTextPredicate(processor);
return new EntryInfo.Widget(() -> widget.setText(info.value == null ? "" : info.value.toString()), widget);
};
}
}

View File

@ -23,7 +23,6 @@ public class JfConfigSafe implements PreLaunchEntrypoint {
String translated = Language.getInstance().get(s);
return translated.equals(s) ? null : translated;
};
ConfigHolderImpl.GSON = LibJf.GSON;
}
public static void registerIfMissing(String modId, Class<?> klazz) {

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.libjf.config.test;
import io.gitlab.jfronny.commons.serialize.gson.GsonIgnore;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.gson.GsonHidden;
import java.util.ArrayList;
import java.util.List;
@ -13,7 +13,8 @@ public class TestConfig implements JfConfig {
@Entry(min = -6) public static float floatTest = -5;
@Entry(max = 21) public static double doubleTest = 20;
@Entry public static String dieStr = "lolz";
@Entry @GsonHidden public static String guiOnlyStr = "lolz";
@Entry @GsonIgnore
public static String guiOnlyStr = "lolz";
public static String gsonOnlyStr = "lolz";
@Entry public static Test enumTest = Test.Test;
@Entry public static List<String> stringList;

View File

@ -1,8 +1,8 @@
package io.gitlab.jfronny.libjf.data.manipulation.api;
import io.gitlab.jfronny.commons.LazySupplier;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.data.manipulation.impl.ResourcePackHook;
import io.gitlab.jfronny.libjf.generic.LazySupplier;
import io.gitlab.jfronny.libjf.generic.ThrowingRunnable;
import io.gitlab.jfronny.libjf.generic.ThrowingSupplier;
import net.fabricmc.fabric.api.event.Event;

View File

@ -1,8 +1,8 @@
package io.gitlab.jfronny.libjf.data.manipulation.impl;
import io.gitlab.jfronny.commons.LazySupplier;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import io.gitlab.jfronny.libjf.generic.LazySupplier;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;

View File

@ -4,6 +4,7 @@ import io.gitlab.jfronny.libjf.HttpUtils;
import io.gitlab.jfronny.libjf.translate.api.TranslateException;
import io.gitlab.jfronny.libjf.translate.api.TranslateService;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.net.URLEncoder;

View File

@ -1,6 +1,8 @@
package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.commons.serialize.gson.GsonHolder;
import io.gitlab.jfronny.libjf.Flags;
import io.gitlab.jfronny.libjf.gson.HiddenAnnotationExclusionStrategy;
import io.gitlab.jfronny.libjf.unsafe.inject.FabricLauncherClassUnlocker;
import io.gitlab.jfronny.libjf.unsafe.inject.KnotClassLoaderInterfaceAccessor;
import net.fabricmc.loader.api.LanguageAdapter;
@ -19,6 +21,8 @@ public class JfLanguageAdapter implements LanguageAdapter {
SafeLog.warn("Unlocking classpath due to: " + flags.stream().map(Flags.BooleanFlag::source).collect(Collectors.joining(", ")));
FabricLoaderImpl.INSTANCE.getGameProvider().unlockClassPath(new FabricLauncherClassUnlocker(new KnotClassLoaderInterfaceAccessor(Thread.currentThread().getContextClassLoader())));
}
HiddenAnnotationExclusionStrategy.register();
GsonHolder.register();
DynamicEntry.execute("libjf:preEarly", UltraEarlyInit.class, s -> s.instance().init());
DynamicEntry.execute("libjf:early", UltraEarlyInit.class, s -> s.instance().init());
SafeLog.info("LibJF unsafe init completed");