aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java2
-rw-r--r--common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java44
-rw-r--r--common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java29
-rw-r--r--test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java52
4 files changed, 97 insertions, 30 deletions
diff --git a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java
index bdb8de8..72254cf 100644
--- a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java
+++ b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java
@@ -138,7 +138,7 @@ public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> {
@Override
public void setValue(T newValue) {
value = newValue;
- group.callListeners();
+ group.callListeners(true);
}
@Override
diff --git a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java
index 12842c1..f45e368 100644
--- a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java
+++ b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.ControllerBuilder;
+import dev.isxander.yacl3.impl.utils.YACLConstants;
import net.minecraft.network.chat.Component;
import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.ApiStatus;
@@ -30,8 +31,10 @@ public final class ListOptionImpl<T> implements ListOption<T> {
private final boolean insertEntriesAtEnd;
private final ImmutableSet<OptionFlag> flags;
private final EntryFactory entryFactory;
+
private final List<BiConsumer<Option<List<T>>, List<T>>> listeners;
private final List<Runnable> refreshListeners;
+ private int listenerTriggerDepth = 0;
public ListOptionImpl(@NotNull Component name, @NotNull OptionDescription description, @NotNull Binding<List<T>> binding, @NotNull Supplier<T> initialValue, @NotNull Function<ListOptionEntry<T>, Controller<T>> controllerFunction, ImmutableSet<OptionFlag> flags, boolean collapsed, boolean available, int minimumNumberOfEntries, int maximumNumberOfEntries, boolean insertEntriesAtEnd, Collection<BiConsumer<Option<List<T>>, List<T>>> listeners) {
this.name = name;
@@ -49,7 +52,7 @@ public final class ListOptionImpl<T> implements ListOption<T> {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
this.refreshListeners = new ArrayList<>();
- callListeners();
+ callListeners(true);
}
@Override
@@ -170,7 +173,12 @@ public final class ListOptionImpl<T> implements ListOption<T> {
@Override
public void setAvailable(boolean available) {
+ boolean changed = this.available != available;
+
this.available = available;
+
+ if (changed)
+ callListeners(false);
}
@Override
@@ -205,14 +213,30 @@ public final class ListOptionImpl<T> implements ListOption<T> {
return values.stream().map(entryFactory::create).collect(Collectors.toList());
}
- void callListeners() {
+ void callListeners(boolean bypass) {
List<T> pendingValue = pendingValue();
- this.listeners.forEach(listener -> listener.accept(this, pendingValue));
+ if (bypass || listenerTriggerDepth == 0) {
+ if (listenerTriggerDepth > 10) {
+ throw new IllegalStateException("Listener trigger depth exceeded 10! This means a listener triggered a listener etc etc 10 times deep. This is likely a bug in the mod using YACL!");
+ }
+
+ this.listenerTriggerDepth++;
+
+ for (BiConsumer<Option<List<T>>, List<T>> listener : listeners) {
+ try {
+ listener.accept(this, pendingValue);
+ } catch (Exception e) {
+ YACLConstants.LOGGER.error("Exception whilst triggering listener for option '%s'".formatted(name.getString()), e);
+ }
+ }
+
+ this.listenerTriggerDepth--;
+ }
}
private void onRefresh() {
refreshListeners.forEach(Runnable::run);
- callListeners();
+ callListeners(true);
}
private class EntryFactory {
@@ -234,7 +258,7 @@ public final class ListOptionImpl<T> implements ListOption<T> {
private Function<ListOptionEntry<T>, Controller<T>> controllerFunction;
private Binding<List<T>> binding = null;
private final Set<OptionFlag> flags = new HashSet<>();
- private T initialValue;
+ private Supplier<T> initialValue;
private boolean collapsed = false;
private boolean available = true;
private int minimumNumberOfEntries = 0;
@@ -259,7 +283,7 @@ public final class ListOptionImpl<T> implements ListOption<T> {
}
@Override
- public Builder<T> initial(@NotNull T initialValue) {
+ public Builder<T> initial(@NotNull Supplier<T> initialValue) {
Validate.notNull(initialValue, "`initialValue` cannot be empty");
this.initialValue = initialValue;
@@ -267,6 +291,14 @@ public final class ListOptionImpl<T> implements ListOption<T> {
}
@Override
+ public Builder<T> initial(@NotNull T initialValue) {
+ Validate.notNull(initialValue, "`initialValue` cannot be empty");
+
+ this.initialValue = () -> initialValue;
+ return this;
+ }
+
+ @Override
public Builder<T> controller(@NotNull Function<Option<T>, ControllerBuilder<T>> controller) {
Validate.notNull(controller, "`controller` cannot be null");
diff --git a/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java
index 01a6287..165f38d 100644
--- a/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java
+++ b/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java
@@ -3,6 +3,7 @@ package dev.isxander.yacl3.impl;
import com.google.common.collect.ImmutableSet;
import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.ControllerBuilder;
+import dev.isxander.yacl3.impl.utils.YACLConstants;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import org.apache.commons.lang3.Validate;
@@ -28,6 +29,7 @@ public final class OptionImpl<T> implements Option<T> {
private T pendingValue;
private final List<BiConsumer<Option<T>, T>> listeners;
+ private int listenerTriggerDepth = 0;
public OptionImpl(
@NotNull Component name,
@@ -82,7 +84,12 @@ public final class OptionImpl<T> implements Option<T> {
@Override
public void setAvailable(boolean available) {
+ boolean changed = this.available != available;
+
this.available = available;
+
+ if (changed)
+ this.triggerListeners(false);
}
@Override
@@ -103,7 +110,7 @@ public final class OptionImpl<T> implements Option<T> {
@Override
public void requestSet(T value) {
pendingValue = value;
- listeners.forEach(listener -> listener.accept(this, pendingValue));
+ this.triggerListeners(true);
}
@Override
@@ -135,6 +142,26 @@ public final class OptionImpl<T> implements Option<T> {
this.listeners.add(changedListener);
}
+ private void triggerListeners(boolean bypass) {
+ if (bypass || listenerTriggerDepth == 0) {
+ if (listenerTriggerDepth > 10) {
+ throw new IllegalStateException("Listener trigger depth exceeded 10! This means a listener triggered a listener etc etc 10 times deep. This is likely a bug in the mod using YACL!");
+ }
+
+ this.listenerTriggerDepth++;
+
+ for (BiConsumer<Option<T>, T> listener : listeners) {
+ try {
+ listener.accept(this, pendingValue);
+ } catch (Exception e) {
+ YACLConstants.LOGGER.error("Exception whilst triggering listener for option '%s'".formatted(name.getString()), e);
+ }
+ }
+
+ this.listenerTriggerDepth--;
+ }
+ }
+
@ApiStatus.Internal
public static class BuilderImpl<T> implements Builder<T> {
private Component name = Component.literal("Name not specified!").withStyle(ChatFormatting.RED);
diff --git a/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java b/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java
index 634c2ce..c490799 100644
--- a/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java
+++ b/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java
@@ -17,6 +17,7 @@ import dev.isxander.yacl3.gui.controllers.string.number.DoubleFieldController;
import dev.isxander.yacl3.gui.controllers.string.number.FloatFieldController;
import dev.isxander.yacl3.gui.controllers.string.number.IntegerFieldController;
import dev.isxander.yacl3.gui.controllers.string.number.LongFieldController;
+import net.minecraft.Util;
import net.minecraft.client.GraphicsStatus;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.toasts.SystemToast;
@@ -29,6 +30,7 @@ import net.minecraft.resources.ResourceLocation;
import java.awt.Color;
import java.nio.file.Path;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
public class GuiTest {
public static Screen getModConfigScreenFactory(Screen parent) {
@@ -53,6 +55,8 @@ public class GuiTest {
}
private static Screen getFullTestSuite(Screen parent) {
+ AtomicReference<Option<Boolean>> booleanOption = new AtomicReference<>();
+
return YetAnotherConfigLib.create(ConfigTest.GSON, (defaults, config, builder) -> builder
.title(Component.literal("Test GUI"))
.category(ConfigCategory.createBuilder()
@@ -60,26 +64,30 @@ public class GuiTest {
.tooltip(Component.literal("Example Category Description"))
.group(OptionGroup.createBuilder()
.name(Component.literal("Boolean Controllers"))
- .option(Option.<Boolean>createBuilder()
- .name(Component.literal("Boolean Toggle"))
- .description(OptionDescription.createBuilder()
- .text(Component.empty()
- .append(Component.literal("a").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("a")))))
- .append(Component.literal("b").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("b")))))
- .append(Component.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("c")))))
- .append(Component.literal("e").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("e")))))
- .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev")))
- )
- .webpImage(Path.of("D:\\Xander\\Code\\isXander\\Controlify\\src\\main\\resources\\assets\\controlify\\textures\\screenshots\\reach-around-placement.webp"), new ResourceLocation("yacl", "e.webp"))
- .build())
- .binding(
- defaults.booleanToggle,
- () -> config.booleanToggle,
- (value) -> config.booleanToggle = value
- )
- .controller(BooleanControllerBuilder::create)
- .flag(OptionFlag.GAME_RESTART)
- .build())
+ .option(Util.make(() -> {
+ var opt = Option.<Boolean>createBuilder()
+ .name(Component.literal("Boolean Toggle"))
+ .description(OptionDescription.createBuilder()
+ .text(Component.empty()
+ .append(Component.literal("a").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("a")))))
+ .append(Component.literal("b").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("b")))))
+ .append(Component.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("c")))))
+ .append(Component.literal("e").withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("e")))))
+ .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev")))
+ )
+ .webpImage(Path.of("D:\\Xander\\Code\\isXander\\Controlify\\src\\main\\resources\\assets\\controlify\\textures\\screenshots\\reach-around-placement.webp"), new ResourceLocation("yacl", "e.webp"))
+ .build())
+ .binding(
+ defaults.booleanToggle,
+ () -> config.booleanToggle,
+ (value) -> config.booleanToggle = value
+ )
+ .controller(BooleanControllerBuilder::create)
+ .flag(OptionFlag.GAME_RESTART)
+ .build();
+ booleanOption.set(opt);
+ return opt;
+ }))
.option(Option.<Boolean>createBuilder()
.name(Component.literal("Custom Boolean Toggle"))
.description(val -> OptionDescription.createBuilder()
@@ -94,9 +102,9 @@ public class GuiTest {
.controller(opt -> BooleanControllerBuilder.create(opt)
.valueFormatter(state -> state ? Component.literal("Amazing") : Component.literal("Not Amazing"))
.coloured(true))
- .available(false)
+ .listener((opt, val) -> booleanOption.get().setAvailable(val))
.build())
- .option(Option.createBuilder(boolean.class)
+ .option(Option.<Boolean>createBuilder()
.name(Component.literal("Tick Box"))
.description(OptionDescription.of(Component.literal("There are even alternate methods of displaying the same data type!")))
.binding(