[config] Remove EntryInfo data class
This commit is contained in:
parent
10d62e5a61
commit
b655c6ab67
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -7,5 +7,6 @@ import java.lang.annotation.Target;
|
|||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Deprecated(forRemoval = true)
|
||||
public @interface GsonHidden {
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}));
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue