From 04fe933f4c24817100f3101f088accf55a621f8a Mon Sep 17 00:00:00 2001 From: isxander Date: Thu, 11 Apr 2024 18:43:06 +0100 Subject: Extremely fragile and broken multiversion build with stonecutter --- .../config/v2/impl/ConfigClassHandlerImpl.java | 274 ++++++++++++++++++++ .../yacl3/config/v2/impl/ConfigFieldImpl.java | 75 ++++++ .../yacl3/config/v2/impl/FieldBackedBinding.java | 22 ++ .../config/v2/impl/ReflectionFieldAccess.java | 49 ++++ .../yacl3/config/v2/impl/autogen/AutoGenUtils.java | 54 ++++ .../yacl3/config/v2/impl/autogen/BooleanImpl.java | 25 ++ .../config/v2/impl/autogen/ColorFieldImpl.java | 19 ++ .../config/v2/impl/autogen/DoubleFieldImpl.java | 32 +++ .../config/v2/impl/autogen/DoubleSliderImpl.java | 33 +++ .../yacl3/config/v2/impl/autogen/DropdownImpl.java | 19 ++ .../v2/impl/autogen/EmptyCustomImageFactory.java | 17 ++ .../config/v2/impl/autogen/EnumCyclerImpl.java | 42 ++++ .../config/v2/impl/autogen/FloatFieldImpl.java | 32 +++ .../config/v2/impl/autogen/FloatSliderImpl.java | 33 +++ .../yacl3/config/v2/impl/autogen/IntFieldImpl.java | 28 +++ .../config/v2/impl/autogen/IntSliderImpl.java | 29 +++ .../config/v2/impl/autogen/ItemFieldImpl.java | 17 ++ .../yacl3/config/v2/impl/autogen/LabelImpl.java | 16 ++ .../config/v2/impl/autogen/ListGroupImpl.java | 102 ++++++++ .../config/v2/impl/autogen/LongFieldImpl.java | 28 +++ .../config/v2/impl/autogen/LongSliderImpl.java | 29 +++ .../config/v2/impl/autogen/MasterTickBoxImpl.java | 25 ++ .../config/v2/impl/autogen/OptionAccessImpl.java | 44 ++++ .../v2/impl/autogen/OptionFactoryRegistry.java | 64 +++++ .../config/v2/impl/autogen/StringFieldImpl.java | 16 ++ .../yacl3/config/v2/impl/autogen/TickBoxImpl.java | 16 ++ .../v2/impl/autogen/YACLAutoGenException.java | 11 + .../v2/impl/serializer/GsonConfigSerializer.java | 275 +++++++++++++++++++++ 28 files changed, 1426 insertions(+) create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/ReflectionFieldAccess.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ColorFieldImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DropdownImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EmptyCustomImageFactory.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EnumCyclerImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatFieldImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntFieldImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ItemFieldImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ListGroupImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongFieldImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongSliderImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionAccessImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/StringFieldImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/YACLAutoGenException.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java (limited to 'src/main/java/dev/isxander/yacl3/config/v2/impl') diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java new file mode 100644 index 0000000..813b3ab --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java @@ -0,0 +1,274 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.config.ConfigEntry; +import dev.isxander.yacl3.config.v2.api.*; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionFactoryRegistry; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionAccessImpl; +import dev.isxander.yacl3.config.v2.impl.autogen.YACLAutoGenException; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import dev.isxander.yacl3.platform.YACLPlatform; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ConfigClassHandlerImpl implements ConfigClassHandler { + private final Class configClass; + private final ResourceLocation id; + private final boolean supportsAutoGen; + private final ConfigSerializer serializer; + private final ConfigFieldImpl[] fields; + + private T instance; + private final T defaults; + private final Constructor noArgsConstructor; + + public ConfigClassHandlerImpl(Class configClass, ResourceLocation id, Function, ConfigSerializer> serializerFactory) { + this.configClass = configClass; + this.id = id; + this.supportsAutoGen = id != null && YACLPlatform.getEnvironment().isClient(); + + try { + noArgsConstructor = configClass.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new YACLAutoGenException("Failed to find no-args constructor for config class %s.".formatted(configClass.getName()), e); + } + this.instance = createNewObject(); + this.defaults = createNewObject(); + + detectOldAnnotation(configClass.getDeclaredFields()); + + this.fields = discoverFields(); + this.serializer = serializerFactory.apply(this); + } + + private ConfigFieldImpl[] discoverFields() { + return Arrays.stream(configClass.getDeclaredFields()) + .peek(field -> field.setAccessible(true)) + .filter(field -> field.isAnnotationPresent(SerialEntry.class) || field.isAnnotationPresent(AutoGen.class)) + .map(field -> new ConfigFieldImpl<>( + new ReflectionFieldAccess<>(field, instance), + new ReflectionFieldAccess<>(field, defaults), + this, + field.getAnnotation(SerialEntry.class), + field.getAnnotation(AutoGen.class) + )) + .toArray(ConfigFieldImpl[]::new); + } + + @Override + public T instance() { + return this.instance; + } + + @Override + public T defaults() { + return this.defaults; + } + + @Override + public Class configClass() { + return this.configClass; + } + + @Override + public ConfigFieldImpl[] fields() { + return this.fields; + } + + @Override + public ResourceLocation id() { + return this.id; + } + + @Override + public boolean supportsAutoGen() { + return this.supportsAutoGen; + } + + @Override + public YetAnotherConfigLib generateGui() { + if (!supportsAutoGen()) { + throw new YACLAutoGenException("Auto GUI generation is not supported for this config class. You either need to enable it in the builder or you are attempting to create a GUI in a dedicated server environment."); + } + + boolean hasAutoGenFields = Arrays.stream(fields()).anyMatch(field -> field.autoGen().isPresent()); + + if (!hasAutoGenFields) { + throw new YACLAutoGenException("No fields in this config class are annotated with @AutoGen. You must annotate at least one field with @AutoGen to generate a GUI."); + } + + OptionAccessImpl storage = new OptionAccessImpl(); + Map categories = new LinkedHashMap<>(); + for (ConfigField configField : fields()) { + configField.autoGen().ifPresent(autoGen -> { + CategoryAndGroups groups = categories.computeIfAbsent( + autoGen.category(), + k -> new CategoryAndGroups( + ConfigCategory.createBuilder() + .name(Component.translatable("yacl3.config.%s.category.%s".formatted(id().toString(), k))), + new LinkedHashMap<>() + ) + ); + OptionAddable group = groups.groups().computeIfAbsent(autoGen.group().orElse(""), k -> { + if (k.isEmpty()) + return groups.category(); + return OptionGroup.createBuilder() + .name(Component.translatable("yacl3.config.%s.category.%s.group.%s".formatted(id().toString(), autoGen.category(), k))); + }); + + Option option; + try { + option = createOption(configField, storage); + } catch (Exception e) { + throw new YACLAutoGenException("Failed to create option for field '%s'".formatted(configField.access().name()), e); + } + + storage.putOption(configField.access().name(), option); + group.option(option); + }); + } + storage.checkBadOperations(); + categories.values().forEach(CategoryAndGroups::finaliseGroups); + + YetAnotherConfigLib.Builder yaclBuilder = YetAnotherConfigLib.createBuilder() + .save(this.serializer()::save) + .title(Component.translatable("yacl3.config.%s.title".formatted(this.id().toString()))); + categories.values().forEach(category -> yaclBuilder.category(category.category().build())); + + return yaclBuilder.build(); + } + + private Option createOption(ConfigField configField, OptionAccess storage) { + return OptionFactoryRegistry.createOption(((ReflectionFieldAccess) configField.access()).field(), configField, storage) + .orElseThrow(() -> new YACLAutoGenException("Failed to create option for field %s".formatted(configField.access().name()))); + } + + @Override + public ConfigSerializer serializer() { + return this.serializer; + } + + @Override + public boolean load() { + // create a new instance to load into + T newInstance = createNewObject(); + + // create field accesses for the new object + Map, ReflectionFieldAccess> accessBufferImpl = Arrays.stream(fields()) + .map(field -> new AbstractMap.SimpleImmutableEntry<>( + field, + new ReflectionFieldAccess<>(field.access().field(), newInstance) + )) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + // convert the map into API safe field accesses + Map, FieldAccess> accessBuffer = accessBufferImpl.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // attempt to load the config + ConfigSerializer.LoadResult loadResult = ConfigSerializer.LoadResult.FAILURE; + Throwable error = null; + try { + loadResult = this.serializer().loadSafely(accessBuffer); + } catch (Throwable e) { + // handle any errors later in the loadResult switch case + error = e; + } + + switch (loadResult) { + case DIRTY: + case SUCCESS: + // replace the instance with the newly created one + this.instance = newInstance; + for (ConfigFieldImpl field : fields()) { + // update the field accesses to point to the correct object + ((ConfigFieldImpl) field).setFieldAccess((ReflectionFieldAccess) accessBufferImpl.get(field)); + } + + if (loadResult == ConfigSerializer.LoadResult.DIRTY) { + // if the load result is dirty, we need to save the config again + this.save(); + } + case NO_CHANGE: + return true; + case FAILURE: + YACLConstants.LOGGER.error( + "Unsuccessful load of config class '{}'. The load will be abandoned and config remains unchanged.", + configClass.getSimpleName(), error + ); + } + + return false; + } + + @Override + public void save() { + serializer().save(); + } + + private T createNewObject() { + try { + return noArgsConstructor.newInstance(); + } catch (Exception e) { + throw new YACLAutoGenException("Failed to create instance of config class '%s' with no-args constructor.".formatted(configClass.getName()), e); + } + } + + private void detectOldAnnotation(Field[] fields) { + boolean hasOldConfigEntry = Arrays.stream(fields) + .anyMatch(field -> field.isAnnotationPresent(ConfigEntry.class)); + + Validate.isTrue(!hasOldConfigEntry, "At least one field in %s is still annotated with the deprecated @ConfigEntry annotation. This is incorrect. Use @SerialEntry.".formatted(configClass.getName())); + } + + public static class BuilderImpl implements Builder { + private final Class configClass; + private ResourceLocation id; + private Function, ConfigSerializer> serializerFactory; + + public BuilderImpl(Class configClass) { + this.configClass = configClass; + } + + @Override + public Builder id(ResourceLocation id) { + this.id = id; + return this; + } + + @Override + public Builder serializer(Function, ConfigSerializer> serializerFactory) { + this.serializerFactory = serializerFactory; + return this; + } + + @Override + public ConfigClassHandler build() { + Validate.notNull(serializerFactory, "serializerFactory must not be null"); + Validate.notNull(configClass, "configClass must not be null"); + + return new ConfigClassHandlerImpl<>(configClass, id, serializerFactory); + } + } + + private record CategoryAndGroups(ConfigCategory.Builder category, Map groups) { + private void finaliseGroups() { + groups.forEach((name, group) -> { + if (group instanceof OptionGroup.Builder groupBuilder) { + category.group(groupBuilder.build()); + } + }); + } + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java new file mode 100644 index 0000000..aeed5ac --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java @@ -0,0 +1,75 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.config.v2.api.*; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGenField; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public class ConfigFieldImpl implements ConfigField { + private ReflectionFieldAccess field; + private final ReflectionFieldAccess defaultField; + private final ConfigClassHandler parent; + private final Optional serial; + private final Optional autoGen; + + public ConfigFieldImpl(ReflectionFieldAccess field, ReflectionFieldAccess defaultField, ConfigClassHandler parent, @Nullable SerialEntry config, @Nullable AutoGen autoGen) { + this.field = field; + this.defaultField = defaultField; + this.parent = parent; + + this.serial = config != null + ? Optional.of( + new SerialFieldImpl( + "".equals(config.value()) ? field.name() : config.value(), + "".equals(config.comment()) ? Optional.empty() : Optional.of(config.comment()), + config.required(), + config.nullable() + ) + ) + : Optional.empty(); + this.autoGen = autoGen != null + ? Optional.of( + new AutoGenFieldImpl<>( + autoGen.category(), + "".equals(autoGen.group()) ? Optional.empty() : Optional.of(autoGen.group()) + ) + ) + : Optional.empty(); + } + + @Override + public ReflectionFieldAccess access() { + return field; + } + + public void setFieldAccess(ReflectionFieldAccess field) { + this.field = field; + } + + @Override + public ReflectionFieldAccess defaultAccess() { + return defaultField; + } + + @Override + public ConfigClassHandler parent() { + return parent; + } + + @Override + public Optional serial() { + return this.serial; + } + + @Override + public Optional autoGen() { + return this.autoGen; + } + + private record SerialFieldImpl(String serialName, Optional comment, boolean required, boolean nullable) implements SerialField { + } + private record AutoGenFieldImpl(String category, Optional group) implements AutoGenField { + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java new file mode 100644 index 0000000..f2f36e7 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java @@ -0,0 +1,22 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.api.Binding; +import dev.isxander.yacl3.config.v2.api.FieldAccess; +import dev.isxander.yacl3.config.v2.api.ReadOnlyFieldAccess; + +public record FieldBackedBinding(FieldAccess field, ReadOnlyFieldAccess defaultField) implements Binding { + @Override + public T getValue() { + return field.get(); + } + + @Override + public void setValue(T value) { + field.set(value); + } + + @Override + public T defaultValue() { + return defaultField.get(); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/ReflectionFieldAccess.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/ReflectionFieldAccess.java new file mode 100644 index 0000000..e102344 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/ReflectionFieldAccess.java @@ -0,0 +1,49 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.config.v2.api.FieldAccess; +import dev.isxander.yacl3.config.v2.impl.autogen.YACLAutoGenException; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.Optional; + +public record ReflectionFieldAccess(Field field, Object instance) implements FieldAccess { + @Override + public T get() { + try { + return (T) field.get(instance); + } catch (IllegalAccessException e) { + throw new YACLAutoGenException("Failed to access field '%s'".formatted(name()), e); + } + } + + @Override + public void set(T value) { + try { + field.set(instance, value); + } catch (IllegalAccessException e) { + throw new YACLAutoGenException("Failed to set field '%s'".formatted(name()), e); + } + } + + @Override + public String name() { + return field.getName(); + } + + @Override + public Type type() { + return field.getGenericType(); + } + + @Override + public Class typeClass() { + return (Class) field.getType(); + } + + @Override + public Optional getAnnotation(Class annotationClass) { + return Optional.ofNullable(field.getAnnotation(annotationClass)); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java new file mode 100644 index 0000000..6f614c1 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java @@ -0,0 +1,54 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.ValueFormattableController; +import dev.isxander.yacl3.api.controller.ValueFormatter; +import dev.isxander.yacl3.config.v2.api.ReadOnlyFieldAccess; +import dev.isxander.yacl3.config.v2.api.autogen.CustomFormat; +import dev.isxander.yacl3.config.v2.api.autogen.FormatTranslation; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Optional; +import java.util.function.Supplier; + +@ApiStatus.Internal +public final class AutoGenUtils { + public static void addCustomFormatterToController(ControllerBuilder controller, ReadOnlyFieldAccess field) { + Optional formatter = field.getAnnotation(CustomFormat.class); + Optional translation = field.getAnnotation(FormatTranslation.class); + + if (formatter.isPresent() && translation.isPresent()) { + throw new YACLAutoGenException("'%s': Cannot use both @CustomFormatter and @FormatTranslation on the same field.".formatted(field.name())); + } else if (formatter.isEmpty() && translation.isEmpty()) { + return; + } + + if (!(controller instanceof ValueFormattableController)) { + throw new YACLAutoGenException("Attempted to use @CustomFormatter or @FormatTranslation on an option factory for field '%s' that uses a controller that does not support this.".formatted(field.name())); + } + + ValueFormattableController typedBuilder = (ValueFormattableController) controller; + + formatter.ifPresent(formatterClass -> { + try { + typedBuilder.formatValue((ValueFormatter) formatterClass.value().getConstructor().newInstance()); + } catch (Exception e) { + throw new YACLAutoGenException("'%s': Failed to instantiate formatter class %s.".formatted(field.name(), formatterClass.value().getName()), e); + } + }); + + translation.ifPresent(annotation -> + typedBuilder.formatValue(v -> Component.translatable(annotation.value(), v))); + } + + public static T constructNoArgsClass(Class clazz, Supplier constructorNotFoundConsumer, Supplier constructorFailedConsumer) { + try { + return clazz.getConstructor().newInstance(); + } catch (NoSuchMethodException e) { + throw new YACLAutoGenException(constructorNotFoundConsumer.get(), e); + } catch (Exception e) { + throw new YACLAutoGenException(constructorFailedConsumer.get(), e); + } + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java new file mode 100644 index 0000000..b41836a --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.Boolean; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import net.minecraft.network.chat.Component; + +public class BooleanImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(Boolean annotation, ConfigField field, OptionAccess storage, Option option) { + var builder = BooleanControllerBuilder.create(option) + .coloured(annotation.colored()); + switch (annotation.formatter()) { + case ON_OFF -> builder.onOffFormatter(); + case YES_NO -> builder.yesNoFormatter(); + case TRUE_FALSE -> builder.trueFalseFormatter(); + case CUSTOM -> builder.formatValue(v -> Component.translatable(getTranslationKey(field, "fmt." + v))); + } + return builder; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ColorFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ColorFieldImpl.java new file mode 100644 index 0000000..7910c59 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ColorFieldImpl.java @@ -0,0 +1,19 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ColorControllerBuilder; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.ColorField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; + +import java.awt.Color; + +public class ColorFieldImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(ColorField annotation, ConfigField field, OptionAccess storage, Option option) { + return ColorControllerBuilder.create(option) + .allowAlpha(annotation.allowAlpha()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java new file mode 100644 index 0000000..6445141 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java @@ -0,0 +1,32 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.DoubleFieldControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.DoubleField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class DoubleFieldImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(DoubleField annotation, ConfigField field, OptionAccess storage, Option option) { + return DoubleFieldControllerBuilder.create(option) + .formatValue(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java new file mode 100644 index 0000000..e6dd05d --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java @@ -0,0 +1,33 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.DoubleSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class DoubleSliderImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(DoubleSlider annotation, ConfigField field, OptionAccess storage, Option option) { + return DoubleSliderControllerBuilder.create(option) + .formatValue(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DropdownImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DropdownImpl.java new file mode 100644 index 0000000..c487aab --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DropdownImpl.java @@ -0,0 +1,19 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.DropdownStringControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.Dropdown; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; + +public class DropdownImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(Dropdown annotation, ConfigField field, OptionAccess storage, Option option) { + return DropdownStringControllerBuilder.create(option) + .values(annotation.values()) + .allowEmptyValue(annotation.allowEmptyValue()) + .allowAnyValue(annotation.allowAnyValue()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EmptyCustomImageFactory.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EmptyCustomImageFactory.java new file mode 100644 index 0000000..1500864 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EmptyCustomImageFactory.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.CustomImage; +import dev.isxander.yacl3.gui.image.ImageRenderer; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class EmptyCustomImageFactory implements CustomImage.CustomImageFactory { + + @Override + public CompletableFuture createImage(Object value, ConfigField field, OptionAccess access) { + throw new IllegalStateException(); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EnumCyclerImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EnumCyclerImpl.java new file mode 100644 index 0000000..f15d862 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EnumCyclerImpl.java @@ -0,0 +1,42 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.NameableEnum; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.CyclingListControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.EnumCycler; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import net.minecraft.network.chat.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +public class EnumCyclerImpl extends SimpleOptionFactory> { + @Override + protected ControllerBuilder> createController(EnumCycler annotation, ConfigField> field, OptionAccess storage, Option> option) { + List> values; + + if (option.pendingValue() instanceof EnumCycler.CyclableEnum cyclableEnum) { + values = Arrays.asList(cyclableEnum.allowedValues()); + } else { + Enum[] constants = field.access().typeClass().getEnumConstants(); + values = IntStream.range(0, constants.length) + .filter(ordinal -> annotation.allowedOrdinals().length == 0 || Arrays.stream(annotation.allowedOrdinals()).noneMatch(allowed -> allowed == ordinal)) + .mapToObj(ordinal -> constants[ordinal]) + .toList(); + } + + // EnumController doesn't support filtering + var builder = CyclingListControllerBuilder.create(option) + .values(values); + if (NameableEnum.class.isAssignableFrom(field.access().typeClass())) { + builder.formatValue(v -> ((NameableEnum) v).getDisplayName()); + } else { + builder.formatValue(v -> Component.translatable("yacl3.config.enum.%s.%s".formatted(field.access().typeClass().getSimpleName(), v.name().toLowerCase()))); + } + return builder; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatFieldImpl.java new file mode 100644 index 0000000..acdabd6 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatFieldImpl.java @@ -0,0 +1,32 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.FloatField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class FloatFieldImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(FloatField annotation, ConfigField field, OptionAccess storage, Option option) { + return FloatFieldControllerBuilder.create(option) + .formatValue(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java new file mode 100644 index 0000000..f22302f --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java @@ -0,0 +1,33 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.FloatSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class FloatSliderImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(FloatSlider annotation, ConfigField field, OptionAccess storage, Option option) { + return FloatSliderControllerBuilder.create(option) + .formatValue(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntFieldImpl.java new file mode 100644 index 0000000..a3b759a --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntFieldImpl.java @@ -0,0 +1,28 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.IntegerFieldControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.IntField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class IntFieldImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(IntField annotation, ConfigField field, OptionAccess storage, Option option) { + return IntegerFieldControllerBuilder.create(option) + .formatValue(v -> { + String key = getTranslationKey(field, "fmt." + v); + if (Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.literal(Integer.toString(v)); + }) + .range(annotation.min(), annotation.max()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java new file mode 100644 index 0000000..b570b44 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java @@ -0,0 +1,29 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.IntSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class IntSliderImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(IntSlider annotation, ConfigField field, OptionAccess storage, Option option) { + return IntegerSliderControllerBuilder.create(option) + .formatValue(v -> { + String key = getTranslationKey(field, "fmt." + v); + if (Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.literal(Integer.toString(v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ItemFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ItemFieldImpl.java new file mode 100644 index 0000000..2802f5c --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ItemFieldImpl.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.ItemControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.ItemField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import net.minecraft.world.item.Item; + +public class ItemFieldImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(ItemField annotation, ConfigField field, OptionAccess storage, Option option) { + return ItemControllerBuilder.create(option); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java new file mode 100644 index 0000000..6f9b368 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.LabelOption; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.Label; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import net.minecraft.network.chat.Component; + +public class LabelImpl implements OptionFactory { + @Override + public Option createOption(Label annotation, ConfigField field, OptionAccess optionAccess) { + return LabelOption.create(field.access().get()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ListGroupImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ListGroupImpl.java new file mode 100644 index 0000000..f78d4ba --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ListGroupImpl.java @@ -0,0 +1,102 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.ListOption; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.OptionDescription; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.ListGroup; +import dev.isxander.yacl3.config.v2.api.autogen.OptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.impl.FieldBackedBinding; +import net.minecraft.client.Minecraft; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +public class ListGroupImpl implements OptionFactory> { + @Override + public Option> createOption(ListGroup annotation, ConfigField> field, OptionAccess optionAccess) { + if (field.autoGen().orElseThrow().group().isPresent()) { + throw new YACLAutoGenException("@ListGroup fields ('%s') cannot be inside a group as lists act as groups.".formatted(field.access().name())); + } + + ListGroup.ValueFactory valueFactory = createValueFactory((Class>) annotation.valueFactory()); + ListGroup.ControllerFactory controllerFactory = createControllerFactory((Class>) annotation.controllerFactory()); + + return ListOption.createBuilder() + .name(Component.translatable(this.getTranslationKey(field, null))) + .description(this.description(field)) + .initial(valueFactory::provideNewValue) + .controller(opt -> controllerFactory.createController(annotation, field, optionAccess, opt)) + .binding(new FieldBackedBinding<>(field.access(), field.defaultAccess())) + .minimumNumberOfEntries(annotation.minEntries()) + .maximumNumberOfEntries(annotation.maxEntries() == 0 ? Integer.MAX_VALUE : annotation.maxEntries()) + .insertEntriesAtEnd(annotation.addEntriesToBottom()) + .build(); + } + + private OptionDescription description(ConfigField> field) { + OptionDescription.Builder builder = OptionDescription.createBuilder(); + + String key = this.getTranslationKey(field, "desc"); + if (Language.getInstance().has(key)) { + builder.text(Component.translatable(key)); + } else { + key += "."; + int i = 0; + while (Language.getInstance().has(key + i++)) { + builder.text(Component.translatable(key + i)); + } + } + + String imagePath = "textures/yacl3/" + field.parent().id().getPath() + "/" + field.access().name() + ".webp"; + imagePath = imagePath.toLowerCase().replaceAll("[^a-z0-9/._:-]", "_"); + ResourceLocation imageLocation = new ResourceLocation(field.parent().id().getNamespace(), imagePath); + if (Minecraft.getInstance().getResourceManager().getResource(imageLocation).isPresent()) { + builder.webpImage(imageLocation); + } + + return builder.build(); + } + + private ListGroup.ValueFactory createValueFactory(Class> clazz) { + Constructor> constructor; + try { + constructor = clazz.getConstructor(); + } catch (NoSuchMethodException e) { + throw new YACLAutoGenException("Could not find no-args constructor for `valueFactory` on '%s' for @ListGroup field.".formatted(clazz.getName()), e); + } + + try { + return constructor.newInstance(); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new YACLAutoGenException("Couldn't invoke no-args constructor for `valueFactory` on '%s' for @ListGroup field.".formatted(clazz.getName()), e); + } + } + + private ListGroup.ControllerFactory createControllerFactory(Class> clazz) { + Constructor> constructor; + try { + constructor = clazz.getConstructor(); + } catch (NoSuchMethodException e) { + throw new YACLAutoGenException("Could not find no-args constructor on `controllerFactory`, '%s' for @ListGroup field.".formatted(clazz.getName()), e); + } + + try { + return constructor.newInstance(); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new YACLAutoGenException("Couldn't invoke no-args constructor on `controllerFactory`, '%s' for @ListGroup field.".formatted(clazz.getName()), e); + } + } + + private String getTranslationKey(ConfigField> field, @Nullable String suffix) { + String key = "yacl3.config.%s.%s".formatted(field.parent().id().toString(), field.access().name()); + if (suffix != null) key += "." + suffix; + return key; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongFieldImpl.java new file mode 100644 index 0000000..5da7d20 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongFieldImpl.java @@ -0,0 +1,28 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.LongFieldControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.LongField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class LongFieldImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(LongField annotation, ConfigField field, OptionAccess storage, Option option) { + return LongFieldControllerBuilder.create(option) + .formatValue(v -> { + String key = getTranslationKey(field, "fmt." + v); + if (Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.literal(Long.toString(v)); + }) + .range(annotation.min(), annotation.max()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongSliderImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongSliderImpl.java new file mode 100644 index 0000000..95c5254 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongSliderImpl.java @@ -0,0 +1,29 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.LongSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.LongSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class LongSliderImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(LongSlider annotation, ConfigField field, OptionAccess storage, Option option) { + return LongSliderControllerBuilder.create(option) + .formatValue(v -> { + String key = getTranslationKey(field, "fmt." + v); + if (Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.literal(Long.toString(v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java new file mode 100644 index 0000000..2d37f03 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.MasterTickBox; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; + +public class MasterTickBoxImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(MasterTickBox annotation, ConfigField field, OptionAccess storage, Option option) { + return TickBoxControllerBuilder.create(option); + } + + @Override + protected void listener(MasterTickBox annotation, ConfigField field, OptionAccess storage, Option option, Boolean value) { + for (String child : annotation.value()) { + storage.scheduleOptionOperation(child, childOpt -> { + childOpt.setAvailable(annotation.invert() != value); + }); + } + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionAccessImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionAccessImpl.java new file mode 100644 index 0000000..579f776 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionAccessImpl.java @@ -0,0 +1,44 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class OptionAccessImpl implements OptionAccess { + private final Map> storage = new HashMap<>(); + private final Map>> scheduledOperations = new HashMap<>(); + + @Override + public @Nullable Option getOption(String fieldName) { + return storage.get(fieldName); + } + + @Override + public void scheduleOptionOperation(String fieldName, Consumer> optionConsumer) { + if (storage.containsKey(fieldName)) { + optionConsumer.accept(storage.get(fieldName)); + } else { + scheduledOperations.merge(fieldName, optionConsumer, Consumer::andThen); + } + } + + public void putOption(String fieldName, Option option) { + storage.put(fieldName, option); + + Consumer> consumer = scheduledOperations.remove(fieldName); + if (consumer != null) { + consumer.accept(option); + } + } + + public void checkBadOperations() { + if (!scheduledOperations.isEmpty()) { + YACLConstants.LOGGER.warn("There are scheduled operations on the `OptionAccess` that tried to reference fields that do not exist. The following have been referenced that do not exist: " + String.join(", ", scheduledOperations.keySet())); + } + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java new file mode 100644 index 0000000..4f6e3c7 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java @@ -0,0 +1,64 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.*; +import dev.isxander.yacl3.config.v2.api.autogen.Boolean; +import dev.isxander.yacl3.impl.utils.YACLConstants; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class OptionFactoryRegistry { + private static final Map, OptionFactory> factoryMap = new HashMap<>(); + + static { + registerOptionFactory(TickBox.class, new TickBoxImpl()); + registerOptionFactory(Boolean.class, new BooleanImpl()); + registerOptionFactory(IntSlider.class, new IntSliderImpl()); + registerOptionFactory(LongSlider.class, new LongSliderImpl()); + registerOptionFactory(FloatSlider.class, new FloatSliderImpl()); + registerOptionFactory(DoubleSlider.class, new DoubleSliderImpl()); + registerOptionFactory(IntField.class, new IntFieldImpl()); + registerOptionFactory(LongField.class, new LongFieldImpl()); + registerOptionFactory(FloatField.class, new FloatFieldImpl()); + registerOptionFactory(DoubleField.class, new DoubleFieldImpl()); + registerOptionFactory(EnumCycler.class, new EnumCyclerImpl()); + registerOptionFactory(StringField.class, new StringFieldImpl()); + registerOptionFactory(ColorField.class, new ColorFieldImpl()); + registerOptionFactory(Dropdown.class, new DropdownImpl()); + registerOptionFactory(ItemField.class, new ItemFieldImpl()); + registerOptionFactory(Label.class, new LabelImpl()); + registerOptionFactory(ListGroup.class, new ListGroupImpl<>()); + + registerOptionFactory(MasterTickBox.class, new MasterTickBoxImpl()); + } + + public static void registerOptionFactory(Class annotation, OptionFactory factory) { + factoryMap.put(annotation, factory); + } + + public static Optional> createOption(Field field, ConfigField configField, OptionAccess storage) { + Annotation[] annotations = Arrays.stream(field.getAnnotations()) + .filter(annotation -> factoryMap.containsKey(annotation.annotationType())) + .toArray(Annotation[]::new); + + if (annotations.length != 1) { + YACLConstants.LOGGER.warn("Found {} option factory annotations on field {}, expected 1", annotations.length, field); + + if (annotations.length == 0) { + return Optional.empty(); + } + } + + Annotation annotation = annotations[0]; + // noinspection unchecked + OptionFactory factory = (OptionFactory) factoryMap.get(annotation.annotationType()); + return Optional.of(factory.createOption(annotation, configField, storage)); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/StringFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/StringFieldImpl.java new file mode 100644 index 0000000..96b63a7 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/StringFieldImpl.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.StringControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.StringField; + +public class StringFieldImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(StringField annotation, ConfigField field, OptionAccess storage, Option option) { + return StringControllerBuilder.create(option); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java new file mode 100644 index 0000000..050257c --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.TickBox; + +public class TickBoxImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(TickBox annotation, ConfigField field, OptionAccess storage, Option option) { + return TickBoxControllerBuilder.create(option); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/YACLAutoGenException.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/YACLAutoGenException.java new file mode 100644 index 0000000..68b375d --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/YACLAutoGenException.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +public class YACLAutoGenException extends RuntimeException { + public YACLAutoGenException(String message) { + super(message); + } + + public YACLAutoGenException(String message, Throwa