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 --- .../dev/isxander/yacl3/config/ConfigEntry.java | 15 ++ .../dev/isxander/yacl3/config/ConfigInstance.java | 50 ++++ .../isxander/yacl3/config/GsonConfigInstance.java | 259 +++++++++++++++++++ .../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 ++++++++ .../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 +++++++++++++++++++++ 67 files changed, 3197 insertions(+) create mode 100644 src/main/java/dev/isxander/yacl3/config/ConfigEntry.java create mode 100644 src/main/java/dev/isxander/yacl3/config/ConfigInstance.java create mode 100644 src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java 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 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') diff --git a/src/main/java/dev/isxander/yacl3/config/ConfigEntry.java b/src/main/java/dev/isxander/yacl3/config/ConfigEntry.java new file mode 100644 index 0000000..066cf42 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/ConfigEntry.java @@ -0,0 +1,15 @@ +package dev.isxander.yacl3.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @deprecated Use {@link dev.isxander.yacl3.config.v2.api.SerialEntry} instead. + */ +@Deprecated +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigEntry { +} diff --git a/src/main/java/dev/isxander/yacl3/config/ConfigInstance.java b/src/main/java/dev/isxander/yacl3/config/ConfigInstance.java new file mode 100644 index 0000000..31d4ca2 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/ConfigInstance.java @@ -0,0 +1,50 @@ +package dev.isxander.yacl3.config; + +import java.lang.reflect.InvocationTargetException; + +/** + * Responsible for handing the actual config data type. + * Holds the instance along with a final default instance + * to reference default values for options and should not be changed. + * + * Abstract methods to save and load the class, implementations are responsible for + * how it saves and load. + * + * @param config data type + * @deprecated upgrade to config v2 {@link dev.isxander.yacl3.config.v2.api.ConfigClassHandler} + */ +@Deprecated +public abstract class ConfigInstance { + private final Class configClass; + private final T defaultInstance; + private T instance; + + public ConfigInstance(Class configClass) { + this.configClass = configClass; + + try { + this.defaultInstance = this.instance = configClass.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + throw new IllegalStateException(String.format("Could not create default instance of config for %s. Make sure there is a default constructor!", this.configClass.getSimpleName())); + } + } + + public abstract void save(); + public abstract void load(); + + public T getConfig() { + return this.instance; + } + + protected void setConfig(T instance) { + this.instance = instance; + } + + public T getDefaults() { + return this.defaultInstance; + } + + public Class getConfigClass() { + return this.configClass; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java b/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java new file mode 100644 index 0000000..c47afe2 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java @@ -0,0 +1,259 @@ +package dev.isxander.yacl3.config; + +import com.google.gson.*; +import dev.isxander.yacl3.config.v2.impl.serializer.GsonConfigSerializer; +import dev.isxander.yacl3.gui.utils.ItemRegistryHelper; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.world.item.Item; + +import java.awt.*; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.function.UnaryOperator; + +/** + * Uses GSON to serialize and deserialize config data from JSON to a file. + *

+ * Only fields annotated with {@link ConfigEntry} 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}. + * + * @param config data type + * @deprecated upgrade to config v2 {@link dev.isxander.yacl3.config.v2.api.ConfigClassHandler} with {@link dev.isxander.yacl3.config.v2.api.serializer.GsonConfigSerializerBuilder} + *

+ * {@code
+ * public class MyConfig {
+ *     public static ConfigClassHandler HANDLER = ConfigClassHandler.createBuilder(MyConfig.class)
+ *             .id(new ResourceLocation("modid", "config"))
+ *             .serializer(config -> GsonConfigSerializerBuilder.create(config)
+ *                     .setPath(FabricLoader.getInstance().getConfigDir().resolve("my_mod.json")
+ *                     .build())
+ *             .build();
+ *
+ *     @SerialEntry public boolean myBoolean = true;
+ * }
+ * }
+ * 
+ */ +@Deprecated +public class GsonConfigInstance extends ConfigInstance { + private final Gson gson; + private final Path path; + + @Deprecated + public GsonConfigInstance(Class configClass, Path path) { + this(configClass, path, new GsonBuilder()); + } + + @Deprecated + public GsonConfigInstance(Class configClass, Path path, Gson gson) { + this(configClass, path, gson.newBuilder()); + } + + @Deprecated + public GsonConfigInstance(Class configClass, Path path, UnaryOperator builder) { + this(configClass, path, builder.apply(new GsonBuilder())); + } + + @Deprecated + public GsonConfigInstance(Class configClass, Path path, GsonBuilder builder) { + super(configClass); + this.path = path; + this.gson = builder + .setExclusionStrategies(new ConfigExclusionStrategy()) + /*? if >1.20.4 { *//* + .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter(RegistryAccess.EMPTY)) + *//*? } elif =1.20.4 {*/ + .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter()) + /*? } else {*//* + .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) + *//*?}*/ + .registerTypeHierarchyAdapter(Style.class, /*? if >=1.20.4 {*/new GsonConfigSerializer.StyleTypeAdapter()/*?} else {*//*new Style.Serializer()*//*?}*/) + .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) + .registerTypeHierarchyAdapter(Item.class, new ItemTypeAdapter()) + .serializeNulls() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + } + + private GsonConfigInstance(Class configClass, Path path, Gson gson, boolean fromBuilder) { + super(configClass); + this.path = path; + this.gson = gson; + } + + @Override + public void save() { + try { + YACLConstants.LOGGER.info("Saving {}...", getConfigClass().getSimpleName()); + Files.writeString(path, gson.toJson(getConfig()), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void load() { + try { + if (Files.notExists(path)) { + save(); + return; + } + + YACLConstants.LOGGER.info("Loading {}...", getConfigClass().getSimpleName()); + setConfig(gson.fromJson(Files.readString(path), getConfigClass())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public Path getPath() { + return this.path; + } + + private static class ConfigExclusionStrategy implements ExclusionStrategy { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return fieldAttributes.getAnnotation(ConfigEntry.class) == null; + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return false; + } + } + + public static class ColorTypeAdapter implements JsonSerializer, JsonDeserializer { + @Override + public Color deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + return new Color(jsonElement.getAsInt(), true); + } + + @Override + public JsonElement serialize(Color color, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(color.getRGB()); + } + } + public static class ItemTypeAdapter implements JsonSerializer, JsonDeserializer { + @Override + public Item deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + return ItemRegistryHelper.getItemFromName(jsonElement.getAsString()); + } + + @Override + public JsonElement serialize(Item item, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(BuiltInRegistries.ITEM.getKey(item).toString()); + } + } + + /** + * Creates a builder for a GSON config instance. + * @param configClass the config class + * @return a new builder + * @param the config type + */ + public static Builder createBuilder(Class configClass) { + return new Builder<>(configClass); + } + + public static class Builder { + private final Class configClass; + private Path path; + private UnaryOperator gsonBuilder = builder -> builder + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .serializeNulls() + /*? if >1.20.4 { *//* + .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter(RegistryAccess.EMPTY)) + *//*? } elif =1.20.4 {*/ + .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter()) + /*? } else {*//* + .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) + *//*?}*/ + .registerTypeHierarchyAdapter(Style.class, /*? if >=1.20.4 {*/new GsonConfigSerializer.StyleTypeAdapter()/*?} else {*//*new Style.Serializer()*//*?}*/) + .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) + .registerTypeHierarchyAdapter(Item.class, new ItemTypeAdapter()); + + private Builder(Class configClass) { + this.configClass = configClass; + } + + /** + * Sets the file path to save and load the config from. + */ + public Builder setPath(Path path) { + this.path = path; + return this; + } + + /** + * 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
  • + *
+ * Still respects the exclusion strategy to only serialize {@link ConfigEntry} + * but these can be added to with setExclusionStrategies. + * + * @param gsonBuilder gson builder to use + */ + public Builder overrideGsonBuilder(GsonBuilder gsonBuilder) { + this.gsonBuilder = builder -> gsonBuilder; + return this; + } + + /** + * 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
  • + *
+ * Still respects the exclusion strategy to only serialize {@link ConfigEntry} + * but these can be added to with setExclusionStrategies. + * + * @param gson gson instance to be converted to a builder + */ + public Builder overrideGsonBuilder(Gson gson) { + return this.overrideGsonBuilder(gson.newBuilder()); + } + + /** + * Appends extra configuration to a GSON builder. + * This is the intended way to add functionality to the GSON instance. + *

+ * By default, YACL sets the GSON with the following options: + *

    + *
  • lower_camel_case field naming policy
  • + *
  • null serialization
  • + *
  • {@link Component}, {@link Style} and {@link Color} type adapters
  • + *
+ * + * @param gsonBuilder the function to apply to the builder + */ + public Builder appendGsonBuilder(UnaryOperator gsonBuilder) { + UnaryOperator prev = this.gsonBuilder; + this.gsonBuilder = builder -> gsonBuilder.apply(prev.apply(builder)); + return this; + } + + /** + * Builds the config instance. + * @return the built config instance + */ + public GsonConfigInstance build() { + UnaryOperator gsonBuilder = builder -> this.gsonBuilder.apply(builder) + .addSerializationExclusionStrategy(new ConfigExclusionStrategy()) + .addDeserializationExclusionStrategy(new ConfigExclusionStrategy()); + + return new GsonConfigInstance<>(configClass, path, gsonBuilder.apply(new GsonBuilder()).create(), true); + } + } +} 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 @in