aboutsummaryrefslogtreecommitdiff
path: root/common/src/main
diff options
context:
space:
mode:
authorisXander <xandersmith2008@gmail.com>2023-08-14 23:27:45 +0100
committerisXander <xandersmith2008@gmail.com>2023-08-14 23:27:45 +0100
commitb3355266572deef1a5c3e494ad162c592383e455 (patch)
tree876510a21e27d0052cb7a1501c0295a427c9dc3c /common/src/main
parentd37e147dbb4db44a921533b572aed3e54b5c6a42 (diff)
downloadYetAnotherConfigLib-b3355266572deef1a5c3e494ad162c592383e455.tar.gz
YetAnotherConfigLib-b3355266572deef1a5c3e494ad162c592383e455.tar.bz2
YetAnotherConfigLib-b3355266572deef1a5c3e494ad162c592383e455.zip
More-or-less complete API for YACL auto-gen
Diffstat (limited to 'common/src/main')
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java5
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java12
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java11
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java12
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java11
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java (renamed from common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigEntry.java)8
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java9
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java82
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java14
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java9
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java21
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java18
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java18
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java16
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java11
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java14
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionStorage.java12
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java11
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java83
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java83
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/DefaultOptionFactory.java18
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java22
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/OptionStorageImpl.java37
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java25
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java30
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java30
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java26
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java16
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java27
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java54
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java16
-rw-r--r--common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java33
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java44
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/utils/UndoRedoHelper.java42
34 files changed, 768 insertions, 112 deletions
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<T> {
ConfigField<?>[] fields();
+ ResourceLocation id();
+
YetAnotherConfigLib generateGui();
boolean supportsAutoGen();
@@ -25,6 +28,8 @@ public interface ConfigClassHandler<T> {
}
interface Builder<T> {
+ Builder<T> id(ResourceLocation id);
+
Builder<T> serializer(Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory);
Builder<T> autoGen(boolean autoGen);
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<T> {
- String serialName();
+ FieldAccess<T> access();
- Optional<String> comment();
+ ReadOnlyFieldAccess<T> defaultAccess();
- FieldAccess<T> access();
+ ConfigClassHandler<?> parent();
- @Nullable OptionFactory<T> factory();
+ Optional<SerialField> serial();
- boolean supportsFactory();
+ Optional<AutoGenField<T>> 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> {
- T get();
-
+public interface FieldAccess<T> extends ReadOnlyFieldAccess<T> {
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<T> {
- Option<T> create(ConfigField<T> field);
+import java.lang.annotation.Annotation;
- Class<T> type();
+public interface OptionFactory<A extends Annotation, T> {
+ Option<T> createOption(A annotation, ConfigField<T> field, OptionStorage storage);
+
+ static <A extends Annotation, T> void register(Class<A> annotationClass, OptionFactory<A, T> 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> {
+ T get();
+
+ String name();
+
+ Type type();
+}
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/SerialEntry.java
index 8b95c3f..e5ba001 100644
--- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigEntry.java
+++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java
@@ -1,7 +1,5 @@
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;
@@ -9,10 +7,8 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
-public @interface ConfigEntry {
- Class<? extends OptionFactory<?>> factory() default DefaultOptionFactory.class;
-
- String serialName() default "";
+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<String> 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<A extends Annotation, T> implements OptionFactory<A, T> {
+ @Override
+ public Option<T> createOption(A annotation, ConfigField<T> field, OptionStorage storage) {
+ Option<T> option = Option.<T>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<T> createController(A annotation, ConfigField<T> field, OptionStorage storage, Option<T> option);
+
+ protected MutableComponent name(A annotation, ConfigField<T> field, OptionStorage storage) {
+ return Component.translatable(this.getTranslationKey(field, null));
+ }
+
+ protected OptionDescription.Builder description(T value, A annotation, ConfigField<T> 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<T> field, OptionStorage storage) {
+ return true;
+ }
+
+ protected Set<OptionFlag> flags(A annotation, ConfigField<T> field, OptionStorage storage) {
+ return Set.of();
+ }
+
+ protected void postInit(A annotation, ConfigField<T> field, OptionStorage storage, Option<T> option) {
+
+ }
+
+ protected String getTranslationKey(ConfigField<T> 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<T> {
+ String category();
+
+ Optional<String> 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<Option<?>> 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<T> implements ConfigClassHandler<T> {
private final Class<T> configClass;
+ private final ResourceLocation id;
private final boolean supportsAutoGen;
private final ConfigSerializer<T> serializer;
private final ConfigField<?>[] fields;
private final T instance, defaults;
- public ConfigClassHandlerImpl(Class<T> configClass, Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory, boolean autoGen) {
+ public ConfigClassHandlerImpl(Class<T> configClass, ResourceLocation id, Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory, boolean autoGen) {
this.configClass = configClass;
+ this.id = id;
this.supportsAutoGen = YACLPlatform.getEnvironment().isClient() && autoGen;
try {
@@ -30,8 +39,9 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> {
}
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);
}
@@ -57,6 +67,11 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> {
}
@Override
+ public ResourceLocation id() {
+ return this.id;
+ }
+
+ @Override
public boolean supportsAutoGen() {
return this.supportsAutoGen;
}
@@ -65,7 +80,43 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> {
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<String, CategoryAndGroups> 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 <U> Option<U> createOption(ConfigField<U> 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<T> implements ConfigClassHandler<T> {
public static class BuilderImpl<T> implements Builder<T> {
private final Class<T> configClass;
+ private ResourceLocation id;
private Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory;
private boolean autoGen;
@@ -83,6 +135,12 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> {
}
@Override
+ public Builder<T> id(ResourceLocation id) {
+ this.id = id;
+ return this;
+ }
+
+ @Override
public Builder<T> serializer(Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory) {
this.serializerFactory = serializerFactory;
return this;
@@ -90,12 +148,23 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> {
@Override
public Builder<T> autoGen(boolean autoGen) {
- throw new IllegalArgumentException();
+ this.autoGen = autoGen;
+ return this;
}
@Override
public ConfigClassHandler<T> build() {
- return new ConfigClassHandlerImpl<>(configClass, serializerFactory, autoGen);
+ return new ConfigClassHandlerImpl<>(configClass, id, serializerFactory, autoGen);
+ }
+ }
+
+ private record CategoryAndGroups(ConfigCategory.Builder category, Map<String, OptionAddable> 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<T> implements ConfigField<T> {
- private final @Nullable OptionFactory<T> factory;
- private final String serialName;
- private final Optional<String> comment;
private final FieldAccess<T> field;
- private final boolean autoGen;
+ private final ReadOnlyFieldAccess<T> defaultField;
+ private final ConfigClassHandler<?> parent;
+ private final Optional<SerialField> serial;
+ private final Optional<AutoGenField<T>> autoGen;
- public ConfigFieldImpl(boolean auto, ConfigEntry entry, FieldAccess<T> 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<T> field, ReadOnlyFieldAccess<T> 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<T> access() {
+ return field;
}
@Override
- public Optional<String> comment() {
- return this.comment;
+ public ReadOnlyFieldAccess<T> defaultAccess() {
+ return defaultField;
}
@Override
- public FieldAccess<T> access() {
- return field;
+ public ConfigClassHandler<?> parent() {
+ return parent;
}
@Override
- public @Nullable OptionFactory<T> factory() {
- return factory;
+ public Optional<SerialField> serial() {
+ return this.serial;
}
@Override
- public boolean supportsFactory() {
+ public Optional<AutoGenField<T>> autoGen() {
return this.autoGen;
}
- private OptionFactory<T> makeFactory(Class<? extends OptionFactory<?>> 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<T>) 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<String> comment) implements SerialField {
+ }
+ private record AutoGenFieldImpl<T>(String category, Optional<String> group) implements AutoGenField<T> {
}
}
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<Object> {
- @Override
- public Option<Object> create(ConfigField<Object> field) {
- throw new NotImplementedException();
- }
-
- @Override
- public Class<Object> 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<T>(FieldAccess<T> field, ReadOnlyFieldAccess<T> defaultField) implements Binding<T> {
+ @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<String, Option<?>> storage = new HashMap<>();
+ private final Map<String, Consumer<Option<?>>> scheduledOperations = new HashMap<>();
+
+ @Override
+ public @Nullable Option<?> getOption(String fieldName) {
+ return storage.get(fieldName);
+ }
+
+ @Override
+ public void scheduleOptionOperation(String fieldName, Consumer<Option<?>> 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<Option<?>> 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<Boolean, java.lang.Boolean> {
+ @Override
+ protected ControllerBuilder<java.lang.Boolean> createController(Boolean annotation, ConfigField<java.lang.Boolean> field, OptionStorage storage, Option<java.lang.Boolean> 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<DoubleSlider, Double> {
+ @Override
+ protected ControllerBuilder<Double> createController(DoubleSlider annotation, ConfigField<Double> field, OptionStorage storage, Option<Double> 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<FloatSlider, Float> {
+ @Override
+ protected ControllerBuilder<Float> createController(FloatSlider annotation, ConfigField<Float> field, OptionStorage storage, Option<Float> 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<IntSlider, Integer> {
+ @Override
+ protected ControllerBuilder<Integer> createController(IntSlider annotation, ConfigField<Integer> field, OptionStorage storage, Option<Integer> 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<Label, Component> {
+ @Override
+ public Option<Component> createOption(Label annotation, ConfigField<Component> 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<MasterTickBox, Boolean> {
+ @Override
+ protected ControllerBuilder<Boolean> createController(MasterTickBox annotation, ConfigField<Boolean> field, OptionStorage storage, Option<Boolean> option) {
+ return TickBoxControllerBuilder.create(option);
+ }
+
+ @Override
+ protected void postInit(MasterTickBox annotation, ConfigField<Boolean> field, OptionStorage storage, Option<Boolean> 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<Class<?>, 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 <A extends Annotation, T> void registerOptionFactory(Class<A> annotation, OptionFactory<A, T> factory) {
+ factoryMap.put(annotation, factory);
+ }
+
+ public static <T> Optional<Option<T>> createOption(Field field, ConfigField<T> 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<Annotation, T> factory = (OptionFactory<Annotation, T>) 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<TickBox, Boolean> {
+ @Override
+ protected ControllerBuilder<Boolean> createController(TickBox annotation, ConfigField<Boolean> field, OptionStorage storage, Option<Boolean> 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<T> extends ConfigSerializer<T> {
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<T> extends ConfigSerializer<T> {
List<String> 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<IStringController<
protected int caretPos;
protected int selectionLength;
-
protected int renderOffset;
+ protected UndoRedoHelper undoRedoHelper;
+
protected float ticks;
private final Component emptyText;
@@ -35,7 +37,9 @@ public class StringControllerElement extends ControllerWidget<IStringController<
inputFieldFocused = false;
selectionLength = 0;
emptyText = Component.literal("Click to type...").withStyle(ChatFormatting.GRAY);
- control.option().addListener((opt, val) -> inputField = control.getString());
+ control.option().addListener((opt, val) -> {
+ inputField = control.getString();
+ });
setDimension(dim);
}
@@ -109,6 +113,10 @@ public class StringControllerElement extends ControllerWidget<IStringController<
selectionLength = 0;
}
+// if (undoRedoHelper == null) {
+// undoRedoHelper = new UndoRedoHelper(inputField, caretPos, selectionLength);
+// }
+
return true;
} else {
inputFieldFocused = false;
@@ -187,12 +195,26 @@ public class StringControllerElement extends ControllerWidget<IStringController<
doDelete();
return true;
}
+// case InputConstants.KEY_Z -> {
+// 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<IStringController<
protected boolean doPaste() {
this.write(client.keyboardHandler.getClipboard());
+ updateUndoHistory();
return true;
}
@@ -219,6 +242,7 @@ public class StringControllerElement extends ControllerWidget<IStringController<
if (selectionLength != 0) {
client.keyboardHandler.setClipboard(getSelection());
this.write("");
+ updateUndoHistory();
return true;
}
return false;
@@ -255,9 +279,13 @@ public class StringControllerElement extends ControllerWidget<IStringController<
if (!inputFieldFocused)
return false;
- write(Character.toString(chr));
+ if (!Screen.hasControlDown()) {
+ write(Character.toString(chr));
+ updateUndoHistory();
+ return true;
+ }
- return true;
+ return false;
}
protected void doBackspace() {
@@ -269,6 +297,7 @@ public class StringControllerElement extends ControllerWidget<IStringController<
checkRenderOffset();
}
}
+ updateUndoHistory();
}
protected void doDelete() {
@@ -277,6 +306,7 @@ public class StringControllerElement extends ControllerWidget<IStringController<
} else if (caretPos < inputField.length()) {
modifyInput(builder -> builder.deleteCharAt(caretPos));
}
+ updateUndoHistory();
}
public void write(String string) {
@@ -308,6 +338,10 @@ public class StringControllerElement extends ControllerWidget<IStringController<
return true;
}
+ protected void updateUndoHistory() {
+// undoRedoHelper.save(inputField, caretPos, selectionLength);
+ }
+
public int getUnshiftedLength() {
if (optionNameString.isEmpty())
return getDimension().width() - getXPadding() * 2;
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/utils/UndoRedoHelper.java b/common/src/main/java/dev/isxander/yacl3/gui/utils/UndoRedoHelper.java
new file mode 100644
index 0000000..3328c16
--- /dev/null
+++ b/common/src/main/java/dev/isxander/yacl3/gui/utils/UndoRedoHelper.java
@@ -0,0 +1,42 @@
+package dev.isxander.yacl3.gui.utils;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UndoRedoHelper {
+ private final List<FieldState> 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) {}
+}