aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/dev/isxander/yacl3/config/v2/api
diff options
context:
space:
mode:
authorisxander <xander@isxander.dev>2024-04-11 18:43:06 +0100
committerisxander <xander@isxander.dev>2024-04-11 18:43:06 +0100
commit04fe933f4c24817100f3101f088accf55a621f8a (patch)
treefeff94ca3ab4484160e69a24f4ee38522381950e /src/main/java/dev/isxander/yacl3/config/v2/api
parent831b894fdb7fe3e173d81387c8f6a2402b8ccfa9 (diff)
downloadYetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.tar.gz
YetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.tar.bz2
YetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.zip
Extremely fragile and broken multiversion build with stonecutter
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/config/v2/api')
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java107
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java40
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/ConfigSerializer.java64
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java14
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java36
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java39
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java16
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java32
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java12
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java41
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ColorField.java21
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomDescription.java12
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomFormat.java17
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomImage.java69
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomName.java18
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleField.java46
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java48
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Dropdown.java43
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/EnumCycler.java35
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatField.java46
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java48
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FormatTranslation.java25
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntField.java41
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java35
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ItemField.java17
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java18
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ListGroup.java60
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongField.java41
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/LongSlider.java35
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java26
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionAccess.java35
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionFactory.java40
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java138
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/StringField.java17
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java17
-rw-r--r--src/main/java/dev/isxander/yacl3/config/v2/api/serializer/GsonConfigSerializerBuilder.java98
36 files changed, 1447 insertions, 0 deletions
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 <T> the backing config class to be managed
+ */
+public interface ConfigClassHandler<T> {
+ /**
+ * 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<T> 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<T> serializer();
+
+ /**
+ * Creates a builder for a config class.
+ *
+ * @param configClass the config class to build
+ * @param <T> the type of the config class
+ * @return the builder
+ */
+ static <T> Builder<T> createBuilder(Class<T> configClass) {
+ return new ConfigClassHandlerImpl.BuilderImpl<>(configClass);
+ }
+
+ interface Builder<T> {
+ /**
+ * The unique identifier of this config handler.
+ * The namespace should be your modid.
+ *
+ * @return this builder
+ */
+ Builder<T> id(ResourceLocation id);
+
+ /**
+ * The function to create the serializer for this config class.
+ *
+ * @return this builder
+ */
+ Builder<T> serializer(Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory);
+
+ ConfigClassHandler<T> 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 <T> the field's type
+ */
+public interface ConfigField<T> {
+ /**
+ * Gets the accessor for the field on the main instance.
+ * (Accessed through {@link ConfigClassHandler#instance()})
+ */
+ FieldAccess<T> access();
+
+ /**
+ * Gets the accessor for the field on the default instance.
+ */
+ ReadOnlyFieldAccess<T> defaultAccess();
+
+ /**
+ * @return the parent config class handler that manages this field.
+ */
+ ConfigClassHandler<?> parent();
+
+ /**
+ * The serial entry metadata for this field, if it exists.
+ */
+ Optional<SerialField> serial();
+
+ /**
+ * The auto-gen metadata for this field, if it exists.
+ */
+ Optional<AutoGenField> 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 <T> the config class to be (de)serialized
+ */
+public abstract class ConfigSerializer<T> {
+ protected final ConfigClassHandler<T> config;
+
+ public ConfigSerializer(ConfigClassHandler<T> 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<ConfigField<?>, 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 <T> the type of the field
+ */
+public interface FieldAccess<T> extends ReadOnlyFieldAccess<T> {
+ /**
+ * 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 <T> the type of the field
+ */
+public interface ReadOnlyFieldAccess<T> {
+ /**
+ * @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<T> typeClass();
+
+ <A extends Annotation> Optional<A> getAnnotation(Class<A> 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<String> 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<String> 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.
+ * <p>
+ * 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:
+ * <ul>
+ * <li>true: {@code yacl3.config.$configId.$fieldName.fmt.true}</li>
+ * <li>false: {@code yacl3.config.$configId.$fieldName.fmt.false}</li>
+ * </ul>
+ */
+ 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.
+ * <p>
+ * 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<? extends ValueFormatter<?>> 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.
+ * <p>
+ * The following file formats are supported:
+ * <ul>
+ * <li>{@code .png}</li>
+ * <li>{@code .webp}</li>
+ * <li>{@code .jpg}, {@code .jpeg}</li>
+ * <li>{@code .gif} - <strong>HIGHLY DISCOURAGED DUE TO LARGE FILE SIZE</strong></li>
+ * </ul>
+ * <p>
+ * If left blank, then {@link CustomImage#factory()} is used.
+ */
+ String value() default "";
+
+ /**
+ * The width of the image, in pixels.
+ * <strong>This is only required when using a PNG with {@link CustomImage#value()}</strong>
+ */
+ int width() default 0;
+
+ /**
+ * The width of the image, in pixels.
+ * <strong>This is only required when using a PNG with {@link CustomImage#value()}</strong>
+ */
+ 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}.
+ * <p>
+ * The factory should contain a public, no-args constructor that will be
+ * invoked via reflection.
+ *
+ * @return the class of the factory
+ */
+ Class<? extends CustomImageFactory<?>> factory() default EmptyCustomImageFactory.class;
+
+ interface CustomImageFactory<T> {
+ CompletableFuture<ImageRenderer> createImage(T value, ConfigField<T> 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.
+ * <p>
+ * 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.
+ * <p>
+ * If this is set to {@code -Double.MAX_VALUE}, there will be no minimum.
+ * <p>
+ * If the current value is at this minimum, if available,
+ * the translation key {@code yacl3.config.$configId.$fieldName.fmt.min}
+ * will be used.
+ */