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 --- .../yacl3/config/v2/api/ConfigClassHandler.java | 107 ++++++++++++++++ .../isxander/yacl3/config/v2/api/ConfigField.java | 40 ++++++ .../yacl3/config/v2/api/ConfigSerializer.java | 64 ++++++++++ .../isxander/yacl3/config/v2/api/FieldAccess.java | 14 +++ .../yacl3/config/v2/api/ReadOnlyFieldAccess.java | 36 ++++++ .../isxander/yacl3/config/v2/api/SerialEntry.java | 39 ++++++ .../isxander/yacl3/config/v2/api/SerialField.java | 16 +++ .../yacl3/config/v2/api/autogen/AutoGen.java | 32 +++++ .../yacl3/config/v2/api/autogen/AutoGenField.java | 12 ++ .../yacl3/config/v2/api/autogen/Boolean.java | 41 ++++++ .../yacl3/config/v2/api/autogen/ColorField.java | 21 ++++ .../config/v2/api/autogen/CustomDescription.java | 12 ++ .../yacl3/config/v2/api/autogen/CustomFormat.java | 17 +++ .../yacl3/config/v2/api/autogen/CustomImage.java | 69 +++++++++++ .../yacl3/config/v2/api/autogen/CustomName.java | 18 +++ .../yacl3/config/v2/api/autogen/DoubleField.java | 46 +++++++ .../yacl3/config/v2/api/autogen/DoubleSlider.java | 48 +++++++ .../yacl3/config/v2/api/autogen/Dropdown.java | 43 +++++++ .../yacl3/config/v2/api/autogen/EnumCycler.java | 35 ++++++ .../yacl3/config/v2/api/autogen/FloatField.java | 46 +++++++ .../yacl3/config/v2/api/autogen/FloatSlider.java | 48 +++++++ .../config/v2/api/autogen/FormatTranslation.java | 25 ++++ .../yacl3/config/v2/api/autogen/IntField.java | 41 ++++++ .../yacl3/config/v2/api/autogen/IntSlider.java | 35 ++++++ .../yacl3/config/v2/api/autogen/ItemField.java | 17 +++ .../yacl3/config/v2/api/autogen/Label.java | 18 +++ .../yacl3/config/v2/api/autogen/ListGroup.java | 60 +++++++++ .../yacl3/config/v2/api/autogen/LongField.java | 41 ++++++ .../yacl3/config/v2/api/autogen/LongSlider.java | 35 ++++++ .../yacl3/config/v2/api/autogen/MasterTickBox.java | 26 ++++ .../yacl3/config/v2/api/autogen/OptionAccess.java | 35 ++++++ .../yacl3/config/v2/api/autogen/OptionFactory.java | 40 ++++++ .../config/v2/api/autogen/SimpleOptionFactory.java | 138 +++++++++++++++++++++ .../yacl3/config/v2/api/autogen/StringField.java | 17 +++ .../yacl3/config/v2/api/autogen/TickBox.java | 17 +++ .../serializer/GsonConfigSerializerBuilder.java | 98 +++++++++++++++ 36 files changed, 1447 insertions(+) create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/ConfigSerializer.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ColorField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomDescription.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomFormat.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomImage.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomName.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Dropdown.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/EnumCycler.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FormatTranslation.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ItemField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ListGroup.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongSlider.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionAccess.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionFactory.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/StringField.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java create mode 100644 src/main/java/dev/isxander/yacl3/config/v2/api/serializer/GsonConfigSerializerBuilder.java (limited to 'src/main/java/dev/isxander/yacl3/config/v2/api') diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java b/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java new file mode 100644 index 0000000..d94280f --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java @@ -0,0 +1,107 @@ +package dev.isxander.yacl3.config.v2.api; + +import dev.isxander.yacl3.api.YetAnotherConfigLib; +import dev.isxander.yacl3.config.v2.impl.ConfigClassHandlerImpl; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Function; + +/** + * Represents a handled config class. + * + * @param the backing config class to be managed + */ +public interface ConfigClassHandler { + /** + * Gets the working instance of the config class. + * This should be used to get and set fields like usual. + */ + T instance(); + + /** + * Gets a second instance of the config class that + * should be used to get default values only. No fields + * should be modified in this instance. + */ + T defaults(); + + /** + * Gets the class of the config. + */ + Class configClass(); + + /** + * Get all eligible fields in the config class. + * They could either be annotated with {@link dev.isxander.yacl3.config.v2.api.autogen.AutoGen} + * or {@link SerialEntry}, do not assume that a field has both of these. + */ + ConfigField[] fields(); + + /** + * The unique identifier of this config handler. + */ + ResourceLocation id(); + + /** + * Auto-generates a GUI for this config class. + * This throws an exception if auto-gen is not supported. + */ + YetAnotherConfigLib generateGui(); + + /** + * Whether this config class supports auto-gen. + * If on a dedicated server, this returns false. + */ + boolean supportsAutoGen(); + + /** + * Safely loads the config class using the provided serializer. + * @return if the config was loaded successfully + */ + boolean load(); + + /** + * Safely saves the config class using the provided serializer. + */ + void save(); + + /** + * The serializer for this config class. + * Manages saving and loading of the config with fields + * annotated with {@link SerialEntry}. + * + * @deprecated use {@link #load()} and {@link #save()} instead. + */ + @Deprecated + ConfigSerializer serializer(); + + /** + * Creates a builder for a config class. + * + * @param configClass the config class to build + * @param the type of the config class + * @return the builder + */ + static Builder createBuilder(Class configClass) { + return new ConfigClassHandlerImpl.BuilderImpl<>(configClass); + } + + interface Builder { + /** + * The unique identifier of this config handler. + * The namespace should be your modid. + * + * @return this builder + */ + Builder id(ResourceLocation id); + + /** + * The function to create the serializer for this config class. + * + * @return this builder + */ + Builder serializer(Function, ConfigSerializer> serializerFactory); + + ConfigClassHandler build(); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java new file mode 100644 index 0000000..181a4d4 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java @@ -0,0 +1,40 @@ +package dev.isxander.yacl3.config.v2.api; + +import dev.isxander.yacl3.config.v2.api.autogen.AutoGenField; + +import java.util.Optional; + +/** + * Represents a field in a config class. + * This is used to get all metadata on a field, + * and access the field and its default value. + * + * @param the field's type + */ +public interface ConfigField { + /** + * Gets the accessor for the field on the main instance. + * (Accessed through {@link ConfigClassHandler#instance()}) + */ + FieldAccess access(); + + /** + * Gets the accessor for the field on the default instance. + */ + ReadOnlyFieldAccess defaultAccess(); + + /** + * @return the parent config class handler that manages this field. + */ + ConfigClassHandler parent(); + + /** + * The serial entry metadata for this field, if it exists. + */ + Optional serial(); + + /** + * The auto-gen metadata for this field, if it exists. + */ + Optional autoGen(); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigSerializer.java b/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigSerializer.java new file mode 100644 index 0000000..4ac988c --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigSerializer.java @@ -0,0 +1,64 @@ +package dev.isxander.yacl3.config.v2.api; + +import java.util.Map; + +/** + * The base class for config serializers, + * offering a method to save and load. + * @param the config class to be (de)serialized + */ +public abstract class ConfigSerializer { + protected final ConfigClassHandler config; + + public ConfigSerializer(ConfigClassHandler config) { + this.config = config; + } + + /** + * Saves all fields in the config class. + * This can be done any way as it's abstract, but most + * commonly it is saved to a file. + */ + public abstract void save(); + + /** + * Loads all fields into the config class. + * @param bufferAccessMap a map of the field accesses. instead of directly setting the field with + * {@link ConfigField#access()}, use this parameter. This loads into a temporary object, + * and the class handler handles pushing these changes to the instance. + * @return the result of the load + */ + public LoadResult loadSafely(Map, FieldAccess> bufferAccessMap) { + this.load(); + return LoadResult.NO_CHANGE; + } + + /** + * Loads all fields in the config class. + * + * @deprecated use {@link #loadSafely(Map)} instead. + */ + @Deprecated + public void load() { + throw new IllegalArgumentException("load() is deprecated, use loadSafely() instead."); + } + + public enum LoadResult { + /** + * Indicates that the config was loaded successfully and the temporary object should be applied. + */ + SUCCESS, + /** + * Indicates that the config was not loaded successfully and the load should be abandoned. + */ + FAILURE, + /** + * Indicates that the config has not changed after a load and the temporary object should be ignored. + */ + NO_CHANGE, + /** + * Indicates the config was loaded successfully, but the config should be re-saved straight away. + */ + DIRTY + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java b/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java new file mode 100644 index 0000000..ea30cd8 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java @@ -0,0 +1,14 @@ +package dev.isxander.yacl3.config.v2.api; + +/** + * A writable field instance access. + * + * @param the type of the field + */ +public interface FieldAccess extends ReadOnlyFieldAccess { + /** + * Sets the value of the field. + * @param value the value to set + */ + void set(T value); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java b/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java new file mode 100644 index 0000000..566d60d --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java @@ -0,0 +1,36 @@ +package dev.isxander.yacl3.config.v2.api; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Optional; + +/** + * An abstract interface for accessing properties of an instance of a field. + * You do not need to worry about exceptions as the implementation + * will handle them. + * + * @param the type of the field + */ +public interface ReadOnlyFieldAccess { + /** + * @return the current value of the field. + */ + T get(); + + /** + * @return the name of the field. + */ + String name(); + + /** + * @return the type of the field. + */ + Type type(); + + /** + * @return the class of the field. + */ + Class typeClass(); + + Optional getAnnotation(Class annotationClass); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java b/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java new file mode 100644 index 0000000..94bf785 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java @@ -0,0 +1,39 @@ +package dev.isxander.yacl3.config.v2.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a field as serializable, so it can be used in a {@link ConfigSerializer}. + * Any field without this annotation will not be saved or loaded, but can still be turned + * into an auto-generated option. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SerialEntry { + /** + * The serial name of the field. + * If empty, the serializer will decide the name. + */ + String value() default ""; + + /** + * The comment to add to the field. + * Some serializers may not support this. + * If empty, the serializer will not add a comment. + */ + String comment() default ""; + + /** + * Whether the field is required in the loaded config to be valid. + * If it's not, the config will be marked as dirty and re-saved with the default value. + */ + boolean required() default true; + + /** + * Whether the field can be null. + */ + boolean nullable() default false; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java new file mode 100644 index 0000000..cf6abfc --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.api; + +import java.util.Optional; + +/** + * The backing interface for the {@link SerialEntry} annotation. + */ +public interface SerialField { + String serialName(); + + Optional comment(); + + boolean required(); + + boolean nullable(); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java new file mode 100644 index 0000000..4187caf --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java @@ -0,0 +1,32 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Any field that is annotated with this will generate a config option + * in the auto-generated config GUI. This should be paired with an + * {@link OptionFactory} annotation to define how to create the option. + * Some examples of this are {@link TickBox}, {@link FloatSlider}, {@link Label} or {@link StringField}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface AutoGen { + /** + * Should be the id of the category. This is used to group options. + * The translation keys also use this. Category IDs can be set as a + * {@code private static final String} and used in the annotation to prevent + * repeating yourself. + */ + String category(); + + /** + * If left blank, the option will go in the root group, where it is + * listed at the top of the category with no group header. If set, + * this also appends to the translation key. Group IDs can be reused + * between multiple categories. + */ + String group() default ""; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java new file mode 100644 index 0000000..7f751fb --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java @@ -0,0 +1,12 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.util.Optional; + +/** + * Backing interface for the {@link AutoGen} annotation. + */ +public interface AutoGenField { + String category(); + + Optional group(); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java new file mode 100644 index 0000000..5598389 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java @@ -0,0 +1,41 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.BooleanControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Boolean { + enum Formatter { + YES_NO, + TRUE_FALSE, + ON_OFF, + /** + * Uses the translation keys: + *

    + *
  • true: {@code yacl3.config.$configId.$fieldName.fmt.true}
  • + *
  • false: {@code yacl3.config.$configId.$fieldName.fmt.false}
  • + *
+ */ + CUSTOM, + } + + /** + * The format used to display the boolean. + */ + Formatter formatter() default Formatter.TRUE_FALSE; + + /** + * Whether to color the formatted text green and red + * depending on the value: true or false respectively. + */ + boolean colored() default false; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ColorField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ColorField.java new file mode 100644 index 0000000..74937b4 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ColorField.java @@ -0,0 +1,21 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.ColorControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ColorField { + /** + * Whether to show/allow the alpha channel in the color field. + */ + boolean allowAlpha() default false; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomDescription.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomDescription.java new file mode 100644 index 0000000..08624b4 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomDescription.java @@ -0,0 +1,12 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface CustomDescription { + String[] value() default ""; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomFormat.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomFormat.java new file mode 100644 index 0000000..15f6336 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomFormat.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.api.controller.ValueFormatter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Allows you to specify a custom {@link ValueFormatter} for a field. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface CustomFormat { + Class> value(); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomImage.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomImage.java new file mode 100644 index 0000000..d193f42 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomImage.java @@ -0,0 +1,69 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.impl.autogen.EmptyCustomImageFactory; +import dev.isxander.yacl3.gui.image.ImageRenderer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Defines a custom image for an option. + * Without this annotation, the option factory will look + * for the resource {@code modid:textures/yacl3/$config_id_path/$fieldName.webp}. + * WEBP was chosen as the default format because file sizes are greatly reduced, + * which is important to keep your JAR size down, if you're so bothered. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface CustomImage { + /** + * The resource path to the image, a {@link net.minecraft.resources.ResourceLocation} + * is constructed with the namespace being the modid of the config, and the path being + * this value. + *

+ * The following file formats are supported: + *

    + *
  • {@code .png}
  • + *
  • {@code .webp}
  • + *
  • {@code .jpg}, {@code .jpeg}
  • + *
  • {@code .gif} - HIGHLY DISCOURAGED DUE TO LARGE FILE SIZE
  • + *
+ *

+ * If left blank, then {@link CustomImage#factory()} is used. + */ + String value() default ""; + + /** + * The width of the image, in pixels. + * This is only required when using a PNG with {@link CustomImage#value()} + */ + int width() default 0; + + /** + * The width of the image, in pixels. + * This is only required when using a PNG with {@link CustomImage#value()} + */ + int height() default 0; + + /** + * The factory to create the image with. + * For the average user, this should not be used as it breaks out of the + * API-safe environment where things could change at any time, but required + * when creating anything advanced with the {@link ImageRenderer}. + *

+ * The factory should contain a public, no-args constructor that will be + * invoked via reflection. + * + * @return the class of the factory + */ + Class> factory() default EmptyCustomImageFactory.class; + + interface CustomImageFactory { + CompletableFuture createImage(T value, ConfigField field, OptionAccess access); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomName.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomName.java new file mode 100644 index 0000000..aa235bb --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomName.java @@ -0,0 +1,18 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Overrides the name of an auto-generated option. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface CustomName { + /** + * The translation key to use for the option's name. + */ + String value() default ""; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleField.java new file mode 100644 index 0000000..963cefd --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleField.java @@ -0,0 +1,46 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.DoubleFieldControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DoubleField { + /** + * The minimum value of the field. If a user enters a value less + * than this, it will be clamped to this value. + *

+ * If this is set to {@code -Double.MAX_VALUE}, there will be no minimum. + *

+ * If the current value is at this minimum, if available, + * the translation key {@code yacl3.config.$configId.$fieldName.fmt.min} + * will be used. + */ + double min() default -Double.MAX_VALUE; + + /** + * The maximum value of the field. If a user enters a value more + * than this, it will be clamped to this value. + *

+ * If this is set to {@code Double.MAX_VALUE}, there will be no minimum. + *

+ * If the current value is at this maximum, if available, + * the translation key {@code yacl3.config.$configId.$fieldName.fmt.max} + * will be used. + */ + double max() default Double.MAX_VALUE; + + /** + * The format used to display the double. + * This is the syntax used in {@link String#format(String, Object...)}. + */ + String format() default "%.2f"; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java new file mode 100644 index 0000000..268f6a4 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java @@ -0,0 +1,48 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DoubleSlider { + /** + * The minimum value of the slider. + *

+ * If the current value is at this minimum, if available, + * the translation key {@code yacl3.config.$configId.$fieldName.fmt.min} + * will be used. + */ + double min(); + + /** + * The maximum value of the slider. + *

+ * If the current value is at this maximum, if available, + * the translation key {@code yacl3.config.$configId.$fieldName.fmt.max} + * will be used. + */ + double max(); + + /** + * The step size of this slider. + * For example, if this is set to 0.1, the slider will + * increment/decrement by 0.1 when dragging, no less, no more and + * will always be a multiple of 0.1. + */ + double step(); + + /** + * The format used to display the double. + * This is the syntax used in {@link String#format(String, Object...)}. + */ + String format() default "%.2f"; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Dropdown.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Dropdown.java new file mode 100644 index 0000000..44239d5 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Dropdown.java @@ -0,0 +1,43 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.DropdownStringControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Dropdown { + /** + * The allowed values for the field. These will be shown in a dropdown + * that the user can filter and select from. + *

+ * Only values in this list will be accepted and written to the config + * file, unless {@link #allow()} is set to ${@code ALLOW_ANY}. + *

+ * Empty string is a valid value only if it appears in this list, or if + * {@link #allow()} is set to {@code ALLOW_EMPTY} or {@code ALLOW_ANY}. + */ + String[] values(); + + /** + * Whether to accept the empty string as a valid value if it does not + * already appear in {@link #values()}. If it already appears there, + * the value of this does not apply. + */ + boolean allowEmptyValue() default false; + + /** + * Whether to accept any string as a valid value. The list of strings + * supplied in {@link #values()} are only used as dropdown suggestions. + * Empty strings are still prohibited unless the empty string appears in + * {@link #values()} or {@link #allowEmptyValue()}. + */ + boolean allowAnyValue() default false; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/EnumCycler.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/EnumCycler.java new file mode 100644 index 0000000..98d94f9 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/EnumCycler.java @@ -0,0 +1,35 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.api.NameableEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory. + *

+ * This creates a regular option with a {@link dev.isxander.yacl3.api.controller.CyclingListControllerBuilder} + * controller. If the enum implements {@link CyclableEnum}, the allowed values will be used from that, + * rather than every single enum constant in the class. If not, {@link EnumCycler#allowedOrdinals()} is used. + *

+ * There are two methods of formatting for enum values. First, if the enum implements + * {@link dev.isxander.yacl3.api.NameableEnum}, {@link NameableEnum#getDisplayName()} is used. + * Otherwise, the translation key {@code yacl3.config.enum.$enumClassName.$enumName} where + * {@code $enumClassName} is the exact name of the class and {@code $enumName} is equal to the lower + * case of {@link Enum#name()}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface EnumCycler { + /** + * The allowed ordinals of the enum class. If empty, all ordinals are allowed. + * This is only used if the enum does not implement {@link CyclableEnum}. + */ + int[] allowedOrdinals() default {}; + + interface CyclableEnum> { + T[] allowedValues(); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatField.java new file mode 100644 index 0000000..1e7e71e --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatField.java @@ -0,0 +1,46 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface FloatField { + /** + * The minimum value of the field. If a user enters a value less + * than this, it will be clamped to this value. + *

+ * If this is set to {@code -Float.MAX_VALUE}, there will be no minimum. + *

+ * If the current value is at this minimum, if available, + * the translation key {@code yacl3.config.$configId.$fieldName.fmt.min} + * will be used. + */ + float min() default -Float.MAX_VALUE; + + /** + * The maximum value of the field. If a user enters a value more + * than this, it will be clamped to this value. + *

+ * If this is set to {@code Float.MAX_VALUE}, there will be no minimum. + *

+ * If the current value is at this maximum, if available, + * the translation key {@code yacl3.config.$configId.$fieldName.fmt.max} + * will be used. + */ + float max() default Float.MAX_VALUE; + + /** + * The format used to display the float. + * This is the syntax used in {@link String#format(String, Object...)}. + */ + String format() default "%.1f"; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java new file mode 100644 index 0000000..19ae9db --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java @@ -0,0 +1,48 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface FloatSlider { + /** + * The minimum value of the slider. + *

+ * If the current value is at this minimum, if available, + * the translation key {@code yacl3.config.$configId.$fieldName.fmt.min} + * will be used. + */ + float min(); + + /** + * The maximum value of the slider. + *

+ * If the current value is at this maximum, if available, + * the translation key {@code yacl3.config.$configId.$fieldName.fmt.max} + * will be used. + */ + float max(); + + /** + * The step size of this slider. + * For example, if this is set to 0.1, the slider will + * increment/decrement by 0.1 when dragging, no less, no more and + * will always be a multiple of 0.1. + */ + float step(); + + /** + * The format used to display the float. + * This is the syntax used in {@link String#format(String, Object...)}. + */ + String format() default "%.1f"; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FormatTranslation.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FormatTranslation.java new file mode 100644 index 0000000..7cc4ded --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FormatTranslation.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Allows you to specify a custom value formatter + * in the form of a translation key. + *

+ * Without this annotation, the value will be formatted + * according to the option factory, implementation details + * for that should be found in the javadoc for the factory. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface FormatTranslation { + /** + * The translation key for the value formatter. + * One parameter is passed to this key: the option's value, + * using {@link Object#toString()}. + */ + String value() default ""; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntField.java new file mode 100644 index 0000000..9945d01 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntField.java @@ -0,0 +1,41 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.IntegerFieldControllerBuilder} controller. + *

+ * If available, the translation key {@code yacl3.config.$configId.$fieldName.fmt.$value} + * is used where {@code $value} is the current value of the option, for example, {@code 5}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface IntField { + /** + * The minimum value of the field. If a user enters a value less + * than this, it will be clamped to this value. + *

+ * If this is set to {@code Integer.MIN_VALUE}, there will be no minimum. + */ + int min() default Integer.MIN_VALUE; + + /** + * The minimum value of the field. If a user enters a value more + * than this, it will be clamped to this value. + *

+ * If this is set to {@code Integer.MAX_VALUE}, there will be no minimum. + */ + int max() default Integer.MAX_VALUE; + + /** + * The format used to display the integer. + * This is the syntax used in {@link String#format(String, Object...)}. + */ + String format() default "%.0f"; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java new file mode 100644 index 0000000..7fd2282 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java @@ -0,0 +1,35 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder} controller. + *

+ * If available, the translation key {@code yacl3.config.$configId.$fieldName.fmt.$value} + * is used where {@code $value} is the current value of the option, for example, {@code 5}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface IntSlider { + /** + * The minimum value of the slider. + */ + int min(); + + /** + * The maximum value of the slider. + */ + int max(); + + /** + * The format used to display the integer. + * This is the syntax used in {@link String#format(String, Object...)}. + */ + int step(); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ItemField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ItemField.java new file mode 100644 index 0000000..84d2c7a --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ItemField.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.ItemControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ItemField { +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java new file mode 100644 index 0000000..41e026f --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java @@ -0,0 +1,18 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory that creates an instance + * of a {@link dev.isxander.yacl3.api.LabelOption}. + *

+ * The backing field can be private and final and + * must be of type {@link net.minecraft.network.chat.Component}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Label { +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ListGroup.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ListGroup.java new file mode 100644 index 0000000..c664f71 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ListGroup.java @@ -0,0 +1,60 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; + +/** + * An option factory. + *

+ * This creates a List option with a custom controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ListGroup { + /** + * The {@link Class} representing a class that implements {@link ValueFactory}. + * To create a new instance for the list when the user adds a new entry to the list. + * Remember this class can be shared with {@link ControllerFactory} as well. + */ + Class> valueFactory(); + + /** + * The {@link Class} representing a class that implements {@link ControllerBuilder} + * to add a controller to every entry in the list. + * Remember this class can be shared with {@link ValueFactory} as well. + */ + Class> controllerFactory(); + + /** + * The maximum number of entries that can be added to the list. + * Once at this limit, the add button is disabled. + * If this is equal to {@code 0}, there is no limit. + */ + int maxEntries() default 0; + + /** + * The minimum number of entries that must be in the list. + * When at this limit, the remove button of the entries is disabled. + */ + int minEntries() default 0; + + /** + * Whether to add new entries at the bottom of the list rather than the top. + */ + boolean addEntriesToBottom() default false; + + interface ValueFactory { + T provideNewValue(); + } + + interface ControllerFactory { + ControllerBuilder createController(ListGroup annotation, ConfigField> field, OptionAccess storage, Option option); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongField.java new file mode 100644 index 0000000..01c3a7e --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongField.java @@ -0,0 +1,41 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.LongFieldControllerBuilder} controller. + *

+ * If available, the translation key {@code yacl3.config.$configId.$fieldName.fmt.$value} + * is used where {@code $value} is the current value of the option, for example, {@code 5}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface LongField { + /** + * The minimum value of the field. If a user enters a value less + * than this, it will be clamped to this value. + *

+ * If this is set to {@code Long.MIN_VALUE}, there will be no minimum. + */ + long min() default Long.MIN_VALUE; + + /** + * The maximum value of the field. If a user enters a value more + * than this, it will be clamped to this value. + *

+ * If this is set to {@code Long.MAX_VALUE}, there will be no minimum. + */ + long max() default Long.MAX_VALUE; + + /** + * The format used to display the long. + * This is the syntax used in {@link String#format(String, Object...)}. + */ + String format() default "%.0f"; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongSlider.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongSlider.java new file mode 100644 index 0000000..5563bd0 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongSlider.java @@ -0,0 +1,35 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.LongSliderControllerBuilder} controller. + *

+ * If available, the translation key {@code yacl3.config.$configId.$fieldName.fmt.$value} + * is used where {@code $value} is the current value of the option, for example, {@code 5}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface LongSlider { + /** + * The minimum value of the slider. + */ + long min(); + + /** + * The maximum value of the slider. + */ + long max(); + + /** + * The format used to display the integer. + * This is the syntax used in {@link String#format(String, Object...)}. + */ + long step(); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java new file mode 100644 index 0000000..70dee1a --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java @@ -0,0 +1,26 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory like {@link TickBox} but controls + * other options' availability based on its state. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface MasterTickBox { + /** + * The exact names of the fields with {@link AutoGen} annotation + * to control the availability of. + */ + String[] value(); + + /** + * Whether having the tickbox disabled should enable the options + * rather than disable. + */ + boolean invert() default false; +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionAccess.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionAccess.java new file mode 100644 index 0000000..c55afe4 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionAccess.java @@ -0,0 +1,35 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.api.Option; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +/** + * An accessor to all options that are auto-generated + * by the config system. + */ +public interface OptionAccess { + /** + * Gets an option by its field name. + * This could be null if the option hasn't been created yet. It is created + * in order of the fields in the class, so if you are trying to get an option + * lower-down in the class, this will return null. + * + * @param fieldName the exact, case-sensitive name of the field. + * @return the created option, or {@code null} if it hasn't been created yet. + */ + @Nullable Option getOption(String fieldName); + + /** + * Schedules an operation to be performed on an option. + * If the option has already been created, the consumer will be + * accepted immediately upon calling this method, if not, it will + * be added to the queue of operations to be performed on the option + * once it does get created. + * + * @param fieldName the exact, case-sensitive name of the field. + * @param optionConsumer the operation to perform on the option. + */ + void scheduleOptionOperation(String fieldName, Consumer> optionConsumer); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionFactory.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionFactory.java new file mode 100644 index 0000000..515a40b --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionFactory.java @@ -0,0 +1,40 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionFactoryRegistry; + +import java.lang.annotation.Annotation; + +/** + * The backing builder for option factories' annotations. + *

+ * If you want to make a basic option with a controller, it's recommended + * to use {@link SimpleOptionFactory} instead which is a subclass of this. + * + * @param the annotation type + * @param the option's binding type + */ +public interface OptionFactory { + /** + * Creates an option from the given annotation, backing field, and storage. + * + * @param annotation the annotation that fields are annotated with to use this factory + * @param field the backing field + * @param optionAccess the option access to access other options in the GUI + * @return the built option to be added to the group/category + */ + Option createOption(A annotation, ConfigField field, OptionAccess optionAccess); + + /** + * Registers an option factory to be used by configs. + * + * @param annotationClass the class of the annotation to use a factory + * @param factory an instance of the factory + * @param the type of the annotation + * @param the type of the option's binding + */ + static void register(Class annotationClass, OptionFactory factory) { + OptionFactoryRegistry.registerOptionFactory(annotationClass, factory); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java new file mode 100644 index 0000000..f7d807f --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java @@ -0,0 +1,138 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.OptionDescription; +import dev.isxander.yacl3.api.OptionFlag; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.impl.FieldBackedBinding; +import dev.isxander.yacl3.config.v2.impl.autogen.AutoGenUtils; +import dev.isxander.yacl3.config.v2.impl.autogen.EmptyCustomImageFactory; +import dev.isxander.yacl3.config.v2.impl.autogen.YACLAutoGenException; +import net.minecraft.client.Minecraft; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.Set; + +public abstract class SimpleOptionFactory implements OptionFactory { + @Override + public Option createOption(A annotation, ConfigField field, OptionAccess optionAccess) { + Option option = Option.createBuilder() + .name(this.name(annotation, field, optionAccess)) + .description(v -> this.description(v, annotation, field, optionAccess).build()) + .binding(new FieldBackedBinding<>(field.access(), field.defaultAccess())) + .controller(opt -> { + ControllerBuilder builder = this.createController(annotation, field, optionAccess, opt); + + AutoGenUtils.addCustomFormatterToController(builder, field.access()); + + return builder; + }) + .available(this.available(annotation, field, optionAccess)) + .flags(this.flags(annotation, field, optionAccess)) + .listener((opt, v) -> this.listener(annotation, field, optionAccess, opt, v)) + .build(); + + postInit(annotation, field, optionAccess, option); + return option; + } + + protected abstract ControllerBuilder createController(A annotation, ConfigField field, OptionAccess storage, Option option); + + protected MutableComponent name(A annotation, ConfigField field, OptionAccess storage) { + Optional customName = field.access().getAnnotation(CustomName.class); + return Component.translatable(customName.map(CustomName::value).orElse(this.getTranslationKey(field, null))); + } + + protected OptionDescription.Builder description(T value, A annotation, ConfigField field, OptionAccess storage) { + 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 = 1; + while (Language.getInstance().has(key + i)) { + builder.text(Component.translatable(key + i)); + i++; + } + } + + field.access().getAnnotation(CustomDescription.class).ifPresent(customDescription -> { + for (String line : customDescription.value()) { + builder.text(Component.translatable(line)); + } + }); + + Optional imageOverrideOpt = field.access().getAnnotation(CustomImage.class); + if (imageOverrideOpt.isPresent()) { + CustomImage imageOverride = imageOverrideOpt.get(); + + if (!imageOverride.factory().equals(EmptyCustomImageFactory.class)) { + CustomImage.CustomImageFactory imageFactory; + try { + imageFactory = (CustomImage.CustomImageFactory) AutoGenUtils.constructNoArgsClass( + imageOverride.factory(), + () -> "'%s': The factory class on @OverrideImage has no no-args constructor.".formatted(field.access().name()), + () -> "'%s': Failed to instantiate factory class %s.".formatted(field.access().name(), imageOverride.factory().getName()) + ); + } catch (ClassCastException e) { + throw new YACLAutoGenException("'%s': The factory class on @OverrideImage is of incorrect type. Expected %s, got %s.".formatted(field.access().name(), field.access().type().getTypeName(), imageOverride.factory().getTypeParameters()[0].getName())); + } + + builder.customImage(imageFactory.createImage(value, field, storage).thenApply(Optional::of)); + } else if (!imageOverride.value().isEmpty()) { + String path = imageOverride.value(); + ResourceLocation imageLocation = new ResourceLocation(field.parent().id().getNamespace(), path); + String extension = path.substring(path.lastIndexOf('.') + 1); + + switch (extension) { + case "png", "jpg", "jpeg" -> builder.image(imageLocation, imageOverride.width(), imageOverride.height()); + case "webp" -> builder.webpImage(imageLocation); + case "gif" -> builder.gifImage(imageLocation); + default -> throw new YACLAutoGenException("'%s': Invalid image extension '%s' on @OverrideImage. Expected: ('png','jpg','webp','gif')".formatted(field.access().name(), extension)); + } + } else { + throw new YACLAutoGenException("'%s': @OverrideImage has no value or factory class.".formatted(field.access().name())); + } + } else { + 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; + } + + protected boolean available(A annotation, ConfigField field, OptionAccess storage) { + return true; + } + + protected Set flags(A annotation, ConfigField field, OptionAccess storage) { + return Set.of(); + } + + protected void listener(A annotation, ConfigField field, OptionAccess storage, Option option, T value) { + + } + + protected void postInit(A annotation, ConfigField field, OptionAccess storage, Option option) { + + } + + protected 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/api/autogen/StringField.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/StringField.java new file mode 100644 index 0000000..50d638e --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/StringField.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A regular option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.StringControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface StringField { +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java new file mode 100644 index 0000000..0a88c14 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory. + *

+ * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.TickBoxControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface TickBox { +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/api/serializer/GsonConfigSerializerBuilder.java b/src/main/java/dev/isxander/yacl3/config/v2/api/serializer/GsonConfigSerializerBuilder.java new file mode 100644 index 0000000..33003d7 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/api/serializer/GsonConfigSerializerBuilder.java @@ -0,0 +1,98 @@ +package dev.isxander.yacl3.config.v2.api.serializer; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import dev.isxander.yacl3.config.ConfigEntry; +import dev.isxander.yacl3.config.v2.api.ConfigClassHandler; +import dev.isxander.yacl3.config.v2.api.ConfigSerializer; +import dev.isxander.yacl3.config.v2.api.SerialEntry; +import dev.isxander.yacl3.config.v2.impl.serializer.GsonConfigSerializer; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +import java.awt.*; +import java.nio.file.Path; +import java.util.function.UnaryOperator; + +/** + * Uses GSON to serialize and deserialize config data from JSON to a file. + *

+ * Only fields annotated with {@link dev.isxander.yacl3.config.v2.api.SerialEntry} are included in the JSON. + * {@link Component}, {@link Style} and {@link Color} have default type adapters, so there is no need to provide them in your GSON instance. + * GSON is automatically configured to format fields as {@code lower_camel_case}. + *

+ * Optionally, this can also be written under JSON5 spec, allowing comments. + * + * @param config data type + */ +public interface GsonConfigSerializerBuilder { + static GsonConfigSerializerBuilder create(ConfigClassHandler config) { + return new GsonConfigSerializer.Builder<>(config); + } + + /** + * Sets the file path to save and load the config from. + */ + GsonConfigSerializerBuilder setPath(Path path); + + /** + * Sets the GSON instance to use. Overrides all YACL defaults such as: + *

+ * Still respects the exclusion strategy to only serialize {@link ConfigEntry} + * but these can be added to with setExclusionStrategies. + * + * @param gsonBuilder gson builder to use + */ + GsonConfigSerializerBuilder overrideGsonBuilder(GsonBuilder gsonBuilder); + + /** + * Sets the GSON instance to use. Overrides all YACL defaults such as: + *
    + *
  • lower_camel_case field naming policy
  • + *
  • null serialization
  • + *
  • {@link Component}, {@link Style} and {@link Color} type adapters
  • + *
+ * but these can