From b3355266572deef1a5c3e494ad162c592383e455 Mon Sep 17 00:00:00 2001 From: isXander Date: Mon, 14 Aug 2023 23:27:45 +0100 Subject: More-or-less complete API for YACL auto-gen --- .../yacl3/config/v2/api/ConfigClassHandler.java | 5 ++ .../isxander/yacl3/config/v2/api/ConfigEntry.java | 18 ----- .../isxander/yacl3/config/v2/api/ConfigField.java | 12 ++-- .../isxander/yacl3/config/v2/api/FieldAccess.java | 11 +-- .../yacl3/config/v2/api/OptionFactory.java | 12 +++- .../yacl3/config/v2/api/ReadOnlyFieldAccess.java | 11 +++ .../isxander/yacl3/config/v2/api/SerialEntry.java | 14 ++++ .../isxander/yacl3/config/v2/api/SerialField.java | 9 +++ .../yacl3/config/v2/api/SimpleOptionFactory.java | 82 +++++++++++++++++++++ .../yacl3/config/v2/api/autogen/AutoGen.java | 14 ++++ .../yacl3/config/v2/api/autogen/AutoGenField.java | 9 +++ .../yacl3/config/v2/api/autogen/Boolean.java | 21 ++++++ .../yacl3/config/v2/api/autogen/DoubleSlider.java | 18 +++++ .../yacl3/config/v2/api/autogen/FloatSlider.java | 18 +++++ .../yacl3/config/v2/api/autogen/IntSlider.java | 16 +++++ .../yacl3/config/v2/api/autogen/Label.java | 11 +++ .../yacl3/config/v2/api/autogen/MasterTickBox.java | 14 ++++ .../yacl3/config/v2/api/autogen/OptionStorage.java | 12 ++++ .../yacl3/config/v2/api/autogen/TickBox.java | 11 +++ .../config/v2/impl/ConfigClassHandlerImpl.java | 83 ++++++++++++++++++++-- .../yacl3/config/v2/impl/ConfigFieldImpl.java | 83 ++++++++++------------ .../yacl3/config/v2/impl/DefaultOptionFactory.java | 18 ----- .../yacl3/config/v2/impl/FieldBackedBinding.java | 22 ++++++ .../yacl3/config/v2/impl/OptionStorageImpl.java | 37 ++++++++++ .../yacl3/config/v2/impl/autogen/BooleanImpl.java | 25 +++++++ .../config/v2/impl/autogen/DoubleSliderImpl.java | 30 ++++++++ .../config/v2/impl/autogen/FloatSliderImpl.java | 30 ++++++++ .../config/v2/impl/autogen/IntSliderImpl.java | 26 +++++++ .../yacl3/config/v2/impl/autogen/LabelImpl.java | 16 +++++ .../config/v2/impl/autogen/MasterTickBoxImpl.java | 27 +++++++ .../v2/impl/autogen/OptionFactoryRegistry.java | 54 ++++++++++++++ .../yacl3/config/v2/impl/autogen/TickBoxImpl.java | 16 +++++ .../v2/impl/serializer/GsonConfigSerializer.java | 33 +++++---- .../string/StringControllerElement.java | 44 ++++++++++-- .../isxander/yacl3/gui/utils/UndoRedoHelper.java | 42 +++++++++++ 35 files changed, 780 insertions(+), 124 deletions(-) delete mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigEntry.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionStorage.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java delete mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/DefaultOptionFactory.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/OptionStorageImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java create mode 100644 common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl3/gui/utils/UndoRedoHelper.java (limited to 'common/src/main/java') diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java index 22e471f..645a8e8 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java @@ -2,6 +2,7 @@ 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; @@ -14,6 +15,8 @@ public interface ConfigClassHandler { ConfigField[] fields(); + ResourceLocation id(); + YetAnotherConfigLib generateGui(); boolean supportsAutoGen(); @@ -25,6 +28,8 @@ public interface ConfigClassHandler { } interface Builder { + Builder id(ResourceLocation id); + Builder serializer(Function, ConfigSerializer> serializerFactory); Builder autoGen(boolean autoGen); diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigEntry.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigEntry.java deleted file mode 100644 index 8b95c3f..0000000 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigEntry.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.isxander.yacl3.config.v2.api; - -import dev.isxander.yacl3.config.v2.impl.DefaultOptionFactory; - -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 ConfigEntry { - Class> factory() default DefaultOptionFactory.class; - - String serialName() default ""; - - String comment() default ""; -} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java index 26a309f..1cd8739 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java @@ -1,17 +1,17 @@ package dev.isxander.yacl3.config.v2.api; -import org.jetbrains.annotations.Nullable; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGenField; import java.util.Optional; public interface ConfigField { - String serialName(); + FieldAccess access(); - Optional comment(); + ReadOnlyFieldAccess defaultAccess(); - FieldAccess access(); + ConfigClassHandler parent(); - @Nullable OptionFactory factory(); + Optional serial(); - boolean supportsFactory(); + Optional> autoGen(); } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java index aed9801..a961172 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java @@ -1,14 +1,5 @@ package dev.isxander.yacl3.config.v2.api; -import java.lang.reflect.Type; - -public interface FieldAccess { - T get(); - +public interface FieldAccess extends ReadOnlyFieldAccess { void set(T value); - - String name(); - - Type type(); - } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java index aabfcf0..9d53e79 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java @@ -1,9 +1,15 @@ package dev.isxander.yacl3.config.v2.api; import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionFactoryRegistry; -public interface OptionFactory { - Option create(ConfigField field); +import java.lang.annotation.Annotation; - Class type(); +public interface OptionFactory { + Option createOption(A annotation, ConfigField field, OptionStorage storage); + + static void register(Class annotationClass, OptionFactory factory) { + OptionFactoryRegistry.registerOptionFactory(annotationClass, factory); + } } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java new file mode 100644 index 0000000..5b71e58 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.config.v2.api; + +import java.lang.reflect.Type; + +public interface ReadOnlyFieldAccess { + T get(); + + String name(); + + Type type(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java new file mode 100644 index 0000000..e5ba001 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java @@ -0,0 +1,14 @@ +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; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SerialEntry { + String value() default ""; + + String comment() default ""; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java new file mode 100644 index 0000000..01c00d6 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java @@ -0,0 +1,9 @@ +package dev.isxander.yacl3.config.v2.api; + +import java.util.Optional; + +public interface SerialField { + String serialName(); + + Optional comment(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java new file mode 100644 index 0000000..5c6894e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java @@ -0,0 +1,82 @@ +package dev.isxander.yacl3.config.v2.api; + +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.autogen.OptionStorage; +import dev.isxander.yacl3.config.v2.impl.FieldBackedBinding; +import net.minecraft.client.Minecraft; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.lang.annotation.Annotation; +import java.util.Set; + +public abstract class SimpleOptionFactory implements OptionFactory { + @Override + public Option createOption(A annotation, ConfigField field, OptionStorage storage) { + Option option = Option.createBuilder() + .name(this.name(annotation, field, storage)) + .description(v -> this.description(v, annotation, field, storage).build()) + .binding(new FieldBackedBinding<>(field.access(), field.defaultAccess())) + .controller(opt -> this.createController(annotation, field, storage, opt)) + .available(this.available(annotation, field, storage)) + .flags(this.flags(annotation, field, storage)) + .build(); + + postInit(annotation, field, storage, option); + return option; + } + + protected abstract ControllerBuilder createController(A annotation, ConfigField field, OptionStorage storage, Option option); + + protected MutableComponent name(A annotation, ConfigField field, OptionStorage storage) { + return Component.translatable(this.getTranslationKey(field, null)); + } + + protected OptionDescription.Builder description(T value, A annotation, ConfigField field, OptionStorage 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 = 0; + while (Language.getInstance().has(key + i++)) { + builder.text(Component.translatable(key + i)); + } + } + + String imagePath = "textures/yacl3/" + field.parent().id().getPath() + "/" + field.access().name() + ".webp"; + imagePath = imagePath.toLowerCase().replaceAll("[^a-z0-9/._:-]", "_"); + ResourceLocation imageLocation = new ResourceLocation(field.parent().id().getNamespace(), imagePath); + if (Minecraft.getInstance().getResourceManager().getResource(imageLocation).isPresent()) { + builder.webpImage(imageLocation); + } + + return builder; + } + + protected boolean available(A annotation, ConfigField field, OptionStorage storage) { + return true; + } + + protected Set flags(A annotation, ConfigField field, OptionStorage storage) { + return Set.of(); + } + + protected void postInit(A annotation, ConfigField field, OptionStorage 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/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java new file mode 100644 index 0000000..8abcb60 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java @@ -0,0 +1,14 @@ +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 AutoGen { + String category(); + + String group() default ""; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java new file mode 100644 index 0000000..48db22d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java @@ -0,0 +1,9 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.util.Optional; + +public interface AutoGenField { + String category(); + + Optional group(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java new file mode 100644 index 0000000..bb948ac --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.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; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Boolean { + enum Formatter { + YES_NO, + TRUE_FALSE, + ON_OFF, + CUSTOM, + } + + Formatter formatter() default Formatter.TRUE_FALSE; + + boolean colored() default false; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java new file mode 100644 index 0000000..47c7b00 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.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; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DoubleSlider { + double min(); + + double max(); + + double step(); + + String format() default "%.2f"; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java new file mode 100644 index 0000000..8d1d8e5 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.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; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface FloatSlider { + float min(); + + float max(); + + float step(); + + String format() default "%.1f"; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java new file mode 100644 index 0000000..05be857 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java @@ -0,0 +1,16 @@ +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 IntSlider { + int min(); + + int max(); + + int step(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java new file mode 100644 index 0000000..7a8ef22 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java @@ -0,0 +1,11 @@ +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 Label { +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java new file mode 100644 index 0000000..67c311d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java @@ -0,0 +1,14 @@ +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 MasterTickBox { + String[] value(); + + boolean invert() default false; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionStorage.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionStorage.java new file mode 100644 index 0000000..f90fc29 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionStorage.java @@ -0,0 +1,12 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.api.Option; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +public interface OptionStorage { + @Nullable Option getOption(String fieldName); + + void scheduleOptionOperation(String fieldName, Consumer> optionConsumer); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java new file mode 100644 index 0000000..413a32d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java @@ -0,0 +1,11 @@ +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 TickBox { +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java index 62aa9b6..b9274e9 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java @@ -1,24 +1,33 @@ package dev.isxander.yacl3.config.v2.impl; -import dev.isxander.yacl3.api.YetAnotherConfigLib; +import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.config.v2.api.*; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionFactoryRegistry; import dev.isxander.yacl3.platform.YACLPlatform; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import org.apache.commons.lang3.Validate; import java.lang.reflect.Constructor; import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.function.Function; public class ConfigClassHandlerImpl implements ConfigClassHandler { private final Class configClass; + private final ResourceLocation id; private final boolean supportsAutoGen; private final ConfigSerializer serializer; private final ConfigField[] fields; private final T instance, defaults; - public ConfigClassHandlerImpl(Class configClass, Function, ConfigSerializer> serializerFactory, boolean autoGen) { + public ConfigClassHandlerImpl(Class configClass, ResourceLocation id, Function, ConfigSerializer> serializerFactory, boolean autoGen) { this.configClass = configClass; + this.id = id; this.supportsAutoGen = YACLPlatform.getEnvironment().isClient() && autoGen; try { @@ -30,8 +39,9 @@ public class ConfigClassHandlerImpl implements ConfigClassHandler { } this.fields = Arrays.stream(configClass.getDeclaredFields()) - .filter(field -> field.isAnnotationPresent(ConfigEntry.class)) - .map(field -> new ConfigFieldImpl<>(this.supportsAutoGen(), field.getAnnotation(ConfigEntry.class), new ReflectionFieldAccess<>(field, instance))) + .peek(field -> field.setAccessible(true)) + .filter(field -> field.isAnnotationPresent(SerialEntry.class) || field.isAnnotationPresent(AutoGen.class)) + .map(field -> new ConfigFieldImpl<>(new ReflectionFieldAccess<>(field, instance), new ReflectionFieldAccess<>(field, defaults), this, field.getAnnotation(SerialEntry.class), field.getAnnotation(AutoGen.class))) .toArray(ConfigField[]::new); this.serializer = serializerFactory.apply(this); } @@ -56,6 +66,11 @@ public class ConfigClassHandlerImpl implements ConfigClassHandler { return this.fields; } + @Override + public ResourceLocation id() { + return this.id; + } + @Override public boolean supportsAutoGen() { return this.supportsAutoGen; @@ -65,7 +80,43 @@ public class ConfigClassHandlerImpl implements ConfigClassHandler { public YetAnotherConfigLib generateGui() { Validate.isTrue(supportsAutoGen(), "Auto GUI generation is not supported for this config class. You either need to enable it in the builder or you are attempting to create a GUI in a dedicated server environment."); - throw new IllegalStateException(); + OptionStorageImpl storage = new OptionStorageImpl(); + Map categories = new LinkedHashMap<>(); + for (ConfigField configField : fields()) { + configField.autoGen().ifPresent(autoGen -> { + CategoryAndGroups groups = categories.computeIfAbsent( + autoGen.category(), + k -> new CategoryAndGroups( + ConfigCategory.createBuilder() + .name(Component.translatable("yacl3.config.%s.category.%s".formatted(id().toString(), k))), + new LinkedHashMap<>() + ) + ); + OptionAddable group = groups.groups().computeIfAbsent(autoGen.group().orElse(""), k -> { + if (k.isEmpty()) + return groups.category(); + return OptionGroup.createBuilder() + .name(Component.translatable("yacl3.config.%s.category.%s.group.%s".formatted(id().toString(), autoGen.category(), k))); + }); + + Option option = createOption(configField, storage); + storage.putOption(configField.access().name(), option); + group.option(option); + }); + } + categories.values().forEach(CategoryAndGroups::finaliseGroups); + + YetAnotherConfigLib.Builder yaclBuilder = YetAnotherConfigLib.createBuilder() + .save(this.serializer()::serialize) + .title(Component.translatable("yacl3.config.%s.title".formatted(this.id().toString()))); + categories.values().forEach(category -> yaclBuilder.category(category.category().build())); + + return yaclBuilder.build(); + } + + private Option createOption(ConfigField configField, OptionStorage storage) { + return OptionFactoryRegistry.createOption(((ReflectionFieldAccess) configField.access()).field(), configField, storage) + .orElseThrow(() -> new IllegalStateException("Failed to create option for field %s".formatted(configField.access().name()))); } @Override @@ -75,6 +126,7 @@ public class ConfigClassHandlerImpl implements ConfigClassHandler { public static class BuilderImpl implements Builder { private final Class configClass; + private ResourceLocation id; private Function, ConfigSerializer> serializerFactory; private boolean autoGen; @@ -82,6 +134,12 @@ public class ConfigClassHandlerImpl implements ConfigClassHandler { this.configClass = configClass; } + @Override + public Builder id(ResourceLocation id) { + this.id = id; + return this; + } + @Override public Builder serializer(Function, ConfigSerializer> serializerFactory) { this.serializerFactory = serializerFactory; @@ -90,12 +148,23 @@ public class ConfigClassHandlerImpl implements ConfigClassHandler { @Override public Builder autoGen(boolean autoGen) { - throw new IllegalArgumentException(); + this.autoGen = autoGen; + return this; } @Override public ConfigClassHandler build() { - return new ConfigClassHandlerImpl<>(configClass, serializerFactory, autoGen); + return new ConfigClassHandlerImpl<>(configClass, id, serializerFactory, autoGen); + } + } + + private record CategoryAndGroups(ConfigCategory.Builder category, Map groups) { + private void finaliseGroups() { + groups.forEach((name, group) -> { + if (group instanceof OptionGroup.Builder groupBuilder) { + category.group(groupBuilder.build()); + } + }); } } } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java index 68bf4b8..3d79e7e 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java @@ -1,74 +1,69 @@ package dev.isxander.yacl3.config.v2.impl; -import dev.isxander.yacl3.config.v2.api.ConfigEntry; -import dev.isxander.yacl3.config.v2.api.ConfigField; -import dev.isxander.yacl3.config.v2.api.FieldAccess; -import dev.isxander.yacl3.config.v2.api.OptionFactory; -import org.apache.commons.lang3.NotImplementedException; +import dev.isxander.yacl3.config.v2.api.*; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGenField; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Constructor; import java.util.Optional; public class ConfigFieldImpl implements ConfigField { - private final @Nullable OptionFactory factory; - private final String serialName; - private final Optional comment; private final FieldAccess field; - private final boolean autoGen; + private final ReadOnlyFieldAccess defaultField; + private final ConfigClassHandler parent; + private final Optional serial; + private final Optional> autoGen; - public ConfigFieldImpl(boolean auto, ConfigEntry entry, FieldAccess field) { - this.serialName = "".equals(entry.serialName()) ? field.name() : entry.serialName(); - this.comment = "".equals(entry.comment()) ? Optional.empty() : Optional.of(entry.comment()); - this.factory = auto ? makeFactory(entry.factory(), this.serialName) : null; - this.autoGen = auto; + public ConfigFieldImpl(FieldAccess field, ReadOnlyFieldAccess defaultField, ConfigClassHandler parent, @Nullable SerialEntry config, @Nullable AutoGen autoGen) { this.field = field; + this.defaultField = defaultField; + this.parent = parent; + + this.serial = config != null + ? Optional.of( + new SerialFieldImpl( + "".equals(config.value()) ? field.name() : config.value(), + "".equals(config.comment()) ? Optional.empty() : Optional.of(config.comment()) + ) + ) + : Optional.empty(); + this.autoGen = autoGen != null + ? Optional.of( + new AutoGenFieldImpl<>( + autoGen.category(), + "".equals(autoGen.group()) ? Optional.empty() : Optional.of(autoGen.group()) + ) + ) + : Optional.empty(); } @Override - public String serialName() { - return this.serialName; + public FieldAccess access() { + return field; } @Override - public Optional comment() { - return this.comment; + public ReadOnlyFieldAccess defaultAccess() { + return defaultField; } @Override - public FieldAccess access() { - return field; + public ConfigClassHandler parent() { + return parent; } @Override - public @Nullable OptionFactory factory() { - return factory; + public Optional serial() { + return this.serial; } @Override - public boolean supportsFactory() { + public Optional> autoGen() { return this.autoGen; } - private OptionFactory makeFactory(Class> clazz, String name) { - if (clazz.equals(DefaultOptionFactory.class)) { - throw new NotImplementedException("Field '%s' does not have an option factory, but auto-gen is enabled.".formatted(this.serialName())); - } - - Constructor constructor; - - try { - constructor = clazz.getConstructor(String.class); - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Failed to find (String) constructor for option factory %s.".formatted(clazz.getName()), e); - } - - try { - return (OptionFactory) constructor.newInstance(name); - } catch (ClassCastException e) { - throw new IllegalStateException("Failed to cast option factory %s to OptionFactory<%s>.".formatted(clazz.getName(), field.type().getTypeName()), e); - } catch (ReflectiveOperationException e) { - throw new IllegalStateException("Failed to create new option factory (class is '%s')".formatted(clazz.getName()), e); - } + private record SerialFieldImpl(String serialName, Optional comment) implements SerialField { + } + private record AutoGenFieldImpl(String category, Optional group) implements AutoGenField { } } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/DefaultOptionFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/DefaultOptionFactory.java deleted file mode 100644 index e32de00..0000000 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/DefaultOptionFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.isxander.yacl3.config.v2.impl; - -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.config.v2.api.ConfigField; -import dev.isxander.yacl3.config.v2.api.OptionFactory; -import org.apache.commons.lang3.NotImplementedException; - -public class DefaultOptionFactory implements OptionFactory { - @Override - public Option create(ConfigField field) { - throw new NotImplementedException(); - } - - @Override - public Class type() { - throw new NotImplementedException(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java new file mode 100644 index 0000000..f2f36e7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java @@ -0,0 +1,22 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.api.Binding; +import dev.isxander.yacl3.config.v2.api.FieldAccess; +import dev.isxander.yacl3.config.v2.api.ReadOnlyFieldAccess; + +public record FieldBackedBinding(FieldAccess field, ReadOnlyFieldAccess defaultField) implements Binding { + @Override + public T getValue() { + return field.get(); + } + + @Override + public void setValue(T value) { + field.set(value); + } + + @Override + public T defaultValue() { + return defaultField.get(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/OptionStorageImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/OptionStorageImpl.java new file mode 100644 index 0000000..3c75a11 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/OptionStorageImpl.java @@ -0,0 +1,37 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class OptionStorageImpl implements OptionStorage { + private final Map> storage = new HashMap<>(); + private final Map>> scheduledOperations = new HashMap<>(); + + @Override + public @Nullable Option getOption(String fieldName) { + return storage.get(fieldName); + } + + @Override + public void scheduleOptionOperation(String fieldName, Consumer> optionConsumer) { + if (storage.containsKey(fieldName)) { + optionConsumer.accept(storage.get(fieldName)); + } else { + scheduledOperations.merge(fieldName, optionConsumer, Consumer::andThen); + } + } + + public void putOption(String fieldName, Option option) { + storage.put(fieldName, option); + + Consumer> consumer = scheduledOperations.remove(fieldName); + if (consumer != null) { + consumer.accept(option); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java new file mode 100644 index 0000000..0a24cf5 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.Boolean; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.network.chat.Component; + +public class BooleanImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(Boolean annotation, ConfigField field, OptionStorage storage, Option option) { + var builder = BooleanControllerBuilder.create(option) + .coloured(annotation.colored()); + switch (annotation.formatter()) { + case ON_OFF -> builder.onOffFormatter(); + case YES_NO -> builder.yesNoFormatter(); + case TRUE_FALSE -> builder.trueFalseFormatter(); + case CUSTOM -> builder.valueFormatter(v -> Component.translatable(getTranslationKey(field, java.lang.Boolean.toString(v)))); + } + return builder; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java new file mode 100644 index 0000000..87db158 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java @@ -0,0 +1,30 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.DoubleSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class DoubleSliderImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(DoubleSlider annotation, ConfigField field, OptionStorage storage, Option option) { + return DoubleSliderControllerBuilder.create(option) + .valueFormatter(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java new file mode 100644 index 0000000..b856a7a --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java @@ -0,0 +1,30 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.FloatSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class FloatSliderImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(FloatSlider annotation, ConfigField field, OptionStorage storage, Option option) { + return FloatSliderControllerBuilder.create(option) + .valueFormatter(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java new file mode 100644 index 0000000..c03d370 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java @@ -0,0 +1,26 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.IntSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class IntSliderImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(IntSlider annotation, ConfigField field, OptionStorage storage, Option option) { + return IntegerSliderControllerBuilder.create(option) + .valueFormatter(v -> { + String key = getTranslationKey(field, "fmt." + v); + if (Language.getInstance().has(key)) + return Component.translatable(key); + return Component.literal(Integer.toString(v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java new file mode 100644 index 0000000..c36c8b7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.LabelOption; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.OptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.Label; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.network.chat.Component; + +public class LabelImpl implements OptionFactory { + @Override + public Option createOption(Label annotation, ConfigField field, OptionStorage storage) { + return LabelOption.create(field.access().get()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java new file mode 100644 index 0000000..65433a3 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java @@ -0,0 +1,27 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.MasterTickBox; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; + +public class MasterTickBoxImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(MasterTickBox annotation, ConfigField field, OptionStorage storage, Option option) { + return TickBoxControllerBuilder.create(option); + } + + @Override + protected void postInit(MasterTickBox annotation, ConfigField field, OptionStorage storage, Option option) { + option.addListener((opt, val) -> { + for (String child : annotation.value()) { + storage.scheduleOptionOperation(child, childOpt -> { + childOpt.setAvailable(annotation.invert() != val); + }); + } + }); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java new file mode 100644 index 0000000..1762f2d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java @@ -0,0 +1,54 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.OptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.*; +import dev.isxander.yacl3.config.v2.api.autogen.Boolean; +import dev.isxander.yacl3.config.v2.impl.autogen.*; +import dev.isxander.yacl3.impl.utils.YACLConstants; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class OptionFactoryRegistry { + private static final Map, OptionFactory> factoryMap = new HashMap<>(); + + static { + registerOptionFactory(TickBox.class, new TickBoxImpl()); + registerOptionFactory(Boolean.class, new BooleanImpl()); + registerOptionFactory(IntSlider.class, new IntSliderImpl()); + registerOptionFactory(FloatSlider.class, new FloatSliderImpl()); + registerOptionFactory(DoubleSlider.class, new DoubleSliderImpl()); + registerOptionFactory(Label.class, new LabelImpl()); + + registerOptionFactory(MasterTickBox.class, new MasterTickBoxImpl()); + } + + public static void registerOptionFactory(Class annotation, OptionFactory factory) { + factoryMap.put(annotation, factory); + } + + public static Optional> createOption(Field field, ConfigField configField, OptionStorage storage) { + Annotation[] annotations = Arrays.stream(field.getAnnotations()) + .filter(annotation -> factoryMap.containsKey(annotation.annotationType())) + .toArray(Annotation[]::new); + + if (annotations.length != 1) { + YACLConstants.LOGGER.warn("Found {} option factory annotations on field {}, expected 1", annotations.length, field); + + if (annotations.length == 0) { + return Optional.empty(); + } + } + + Annotation annotation = annotations[0]; + // noinspection unchecked + OptionFactory factory = (OptionFactory) factoryMap.get(annotation.annotationType()); + return Optional.of(factory.createOption(annotation, configField, storage)); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java new file mode 100644 index 0000000..202534e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import dev.isxander.yacl3.config.v2.api.autogen.TickBox; + +public class TickBoxImpl extends SimpleOptionFactory { + @Override + protected ControllerBuilder createController(TickBox annotation, ConfigField field, OptionStorage storage, Option option) { + return TickBoxControllerBuilder.create(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java index 8bbc079..712a459 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java @@ -1,10 +1,7 @@ package dev.isxander.yacl3.config.v2.impl.serializer; import com.google.gson.*; -import dev.isxander.yacl3.config.v2.api.ConfigClassHandler; -import dev.isxander.yacl3.config.v2.api.ConfigField; -import dev.isxander.yacl3.config.v2.api.ConfigSerializer; -import dev.isxander.yacl3.config.v2.api.GsonConfigSerializerBuilder; +import dev.isxander.yacl3.config.v2.api.*; import dev.isxander.yacl3.impl.utils.YACLConstants; import dev.isxander.yacl3.platform.YACLPlatform; import net.minecraft.network.chat.Component; @@ -35,14 +32,19 @@ public class GsonConfigSerializer extends ConfigSerializer { JsonObject root = new JsonObject(); for (ConfigField field : config.fields()) { - if (YACLPlatform.isDevelopmentEnv() && field.comment().isPresent()) { - YACLConstants.LOGGER.error("Config field '{}' has a comment, but comments are not supported by Gson. Please remove the comment or switch to a different serializer. This log will not be shown in production.", field.serialName()); + SerialField serial = field.serial().orElse(null); + if (serial == null) { + continue; + } + + if (YACLPlatform.isDevelopmentEnv() && serial.comment().isPresent()) { + YACLConstants.LOGGER.error("Config field '{}' has a comment, but comments are not supported by Gson. Please remove the comment or switch to a different serializer. This log will not be shown in production.", serial.serialName()); } try { - root.add(field.serialName(), gson.toJsonTree(field.access().get())); + root.add(serial.serialName(), gson.toJsonTree(field.access().get())); } catch (Exception e) { - YACLConstants.LOGGER.error("Failed to serialize config field '{}'.", field.serialName(), e); + YACLConstants.LOGGER.error("Failed to serialize config field '{}'.", serial.serialName(), e); } } @@ -75,17 +77,22 @@ public class GsonConfigSerializer extends ConfigSerializer { List unconsumedKeys = new ArrayList<>(root.keySet()); for (ConfigField field : config.fields()) { - if (root.containsKey(field.serialName())) { + SerialField serial = field.serial().orElse(null); + if (serial == null) { + continue; + } + + if (root.containsKey(serial.serialName())) { try { - field.access().set(gson.fromJson(root.get(field.serialName()), field.access().type())); + field.access().set(gson.fromJson(root.get(serial.serialName()), field.access().type())); } catch (Exception e) { - YACLConstants.LOGGER.error("Failed to deserialize config field '{}'.", field.serialName(), e); + YACLConstants.LOGGER.error("Failed to deserialize config field '{}'.", serial.serialName(), e); } } else { - YACLConstants.LOGGER.warn("Config field '{}' was not found in the config file. Skipping.", field.serialName()); + YACLConstants.LOGGER.warn("Config field '{}' was not found in the config file. Skipping.", serial.serialName()); } - unconsumedKeys.remove(field.serialName()); + unconsumedKeys.remove(serial.serialName()); } if (!unconsumedKeys.isEmpty()) { diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java index 86cc7bd..383e188 100644 --- a/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java @@ -5,6 +5,7 @@ import dev.isxander.yacl3.api.utils.Dimension; import dev.isxander.yacl3.gui.YACLScreen; import dev.isxander.yacl3.gui.controllers.ControllerWidget; import dev.isxander.yacl3.gui.utils.GuiUtils; +import dev.isxander.yacl3.gui.utils.UndoRedoHelper; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; @@ -21,9 +22,10 @@ public class StringControllerElement extends ControllerWidget inputField = control.getString()); + control.option().addListener((opt, val) -> { + inputField = control.getString(); + }); setDimension(dim); } @@ -109,6 +113,10 @@ public class StringControllerElement extends ControllerWidget { +// if (Screen.hasControlDown()) { +// UndoRedoHelper.FieldState updated = Screen.hasShiftDown() ? undoRedoHelper.redo() : undoRedoHelper.undo(); +// if (updated != null) { +// System.out.println("Updated: " + updated); +// if (modifyInput(builder -> builder.replace(0, inputField.length(), updated.text()))) { +// caretPos = updated.cursorPos(); +// selectionLength = updated.selectionLength(); +// checkRenderOffset(); +// } +// } +// return true; +// } +// } } if (Screen.isPaste(keyCode)) { return doPaste(); } else if (Screen.isCopy(keyCode)) { - return doCopy(); + return doCopy(); } else if (Screen.isCut(keyCode)) { return doCut(); } else if (Screen.isSelectAll(keyCode)) { @@ -204,6 +226,7 @@ public class StringControllerElement extends ControllerWidget builder.deleteCharAt(caretPos)); } + updateUndoHistory(); } public void write(String string) { @@ -308,6 +338,10 @@ public class StringControllerElement extends ControllerWidget history = new ArrayList<>(); + private int index = 0; + + public UndoRedoHelper(String text, int cursorPos, int selectionLength) { + history.add(new FieldState(text, cursorPos, selectionLength)); + } + + public void save(String text, int cursorPos, int selectionLength) { + int max = history.size(); + history.subList(index, max).clear(); + history.add(new FieldState(text, cursorPos, selectionLength)); + index++; + } + + public @Nullable FieldState undo() { + index--; + index = Math.max(index, 0); + + if (history.isEmpty()) + return null; + return history.get(index); + } + + public @Nullable FieldState redo() { + if (index < history.size() - 1) { + index++; + return history.get(index); + } else { + return null; + } + } + + public record FieldState(String text, int cursorPos, int selectionLength) {} +} -- cgit