aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/dev/isxander/yacl3/impl
diff options
context:
space:
mode:
authorisXander <xander@isxander.dev>2024-10-19 19:22:45 +0100
committerisXander <xander@isxander.dev>2024-10-19 19:22:45 +0100
commite73a08e6672fb380cab8db71340158969c5ef56b (patch)
treedd08a311f4eff9a91b465ef1854caa1286fc6f9a /src/main/java/dev/isxander/yacl3/impl
parent519ac2fc0e23587defcf4a8259979961d35d0ce2 (diff)
downloadYetAnotherConfigLib-e73a08e6672fb380cab8db71340158969c5ef56b.tar.gz
YetAnotherConfigLib-e73a08e6672fb380cab8db71340158969c5ef56b.tar.bz2
YetAnotherConfigLib-e73a08e6672fb380cab8db71340158969c5ef56b.zip
3.6.0
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/impl')
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java20
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java11
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ImmutableStateManager.java56
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/InstantStateManager.java68
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java160
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java12
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java120
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/NotNullBinding.java31
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/OptionImpl.java162
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ProvidesBindingForDeprecation.java7
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/SafeBinding.java29
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/SelfContainedBinding.java32
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/SimpleStateManager.java65
13 files changed, 544 insertions, 229 deletions
diff --git a/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java
index 170b8e0..60a9dc9 100644
--- a/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java
+++ b/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java
@@ -17,10 +17,9 @@ import java.util.function.Consumer;
public final class ButtonOptionImpl implements ButtonOption {
private final Component name;
private final OptionDescription description;
- private final BiConsumer<YACLScreen, ButtonOption> action;
+ private final StateManager<BiConsumer<YACLScreen, ButtonOption>> stateManager;
private boolean available;
private final Controller<BiConsumer<YACLScreen, ButtonOption>> controller;
- private final Binding<BiConsumer<YACLScreen, ButtonOption>> binding;
public ButtonOptionImpl(
@NotNull Component name,
@@ -31,10 +30,9 @@ public final class ButtonOptionImpl implements ButtonOption {
) {
this.name = name;
this.description = description;
- this.action = action;
+ this.stateManager = StateManager.createImmutable(action);
this.available = available;
this.controller = text != null ? new ActionController(this, text) : new ActionController(this);
- this.binding = new EmptyBinderImpl();
}
@Override
@@ -54,7 +52,7 @@ public final class ButtonOptionImpl implements ButtonOption {
@Override
public BiConsumer<YACLScreen, ButtonOption> action() {
- return action;
+ return stateManager().get();
}
@Override
@@ -73,8 +71,13 @@ public final class ButtonOptionImpl implements ButtonOption {
}
@Override
+ public @NotNull StateManager<BiConsumer<YACLScreen, ButtonOption>> stateManager() {
+ return this.stateManager;
+ }
+
+ @Override
public @NotNull Binding<BiConsumer<YACLScreen, ButtonOption>> binding() {
- return binding;
+ return new EmptyBinderImpl();
}
@Override
@@ -118,6 +121,11 @@ public final class ButtonOptionImpl implements ButtonOption {
}
@Override
+ public void addEventListener(OptionEventListener<BiConsumer<YACLScreen, ButtonOption>> listener) {
+
+ }
+
+ @Override
public void addListener(BiConsumer<Option<BiConsumer<YACLScreen, ButtonOption>>, BiConsumer<YACLScreen, ButtonOption>> changedListener) {
}
diff --git a/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java b/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java
index 64588f2..16d8e14 100644
--- a/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java
+++ b/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java
@@ -31,6 +31,11 @@ public class HiddenNameListOptionEntry<T> implements ListOptionEntry<T> {
}
@Override
+ public @NotNull StateManager<T> stateManager() {
+ return option.stateManager();
+ }
+
+ @Override
public @NotNull Controller<T> controller() {
return option.controller();
}
@@ -101,9 +106,13 @@ public class HiddenNameListOptionEntry<T> implements ListOptionEntry<T> {
}
@Override
+ @Deprecated
public void addListener(BiConsumer<Option<T>, T> changedListener) {
option.addListener(changedListener);
}
-
+ @Override
+ public void addEventListener(OptionEventListener<T> listener) {
+ option.addEventListener(listener);
+ }
}
diff --git a/src/main/java/dev/isxander/yacl3/impl/ImmutableStateManager.java b/src/main/java/dev/isxander/yacl3/impl/ImmutableStateManager.java
new file mode 100644
index 0000000..f4fd9b2
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/ImmutableStateManager.java
@@ -0,0 +1,56 @@
+package dev.isxander.yacl3.impl;
+
+import dev.isxander.yacl3.api.StateManager;
+
+public class ImmutableStateManager<T> implements StateManager<T> {
+ private final T value;
+
+ public ImmutableStateManager(T value) {
+ this.value = value;
+ }
+
+ @Override
+ public void set(T value) {
+ throw new UnsupportedOperationException("Cannot set value of immutable state manager");
+ }
+
+ @Override
+ public T get() {
+ return value;
+ }
+
+ @Override
+ public void apply() {
+ // no-op
+ }
+
+ @Override
+ public void resetToDefault(ResetAction action) {
+ // always default
+ }
+
+ @Override
+ public void sync() {
+ // always synced
+ }
+
+ @Override
+ public boolean isSynced() {
+ return true;
+ }
+
+ @Override
+ public boolean isAlwaysSynced() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return true;
+ }
+
+ @Override
+ public void addListener(StateListener<T> stateListener) {
+ // as the values never change, listeners are not needed and would never be called
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/InstantStateManager.java b/src/main/java/dev/isxander/yacl3/impl/InstantStateManager.java
new file mode 100644
index 0000000..8806617
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/InstantStateManager.java
@@ -0,0 +1,68 @@
+package dev.isxander.yacl3.impl;
+
+import dev.isxander.yacl3.api.Binding;
+import dev.isxander.yacl3.api.StateManager;
+
+public class InstantStateManager<T> implements StateManager<T>, ProvidesBindingForDeprecation<T> {
+ private final Binding<T> binding;
+ private StateListener<T> stateListener;
+
+ public InstantStateManager(Binding<T> binding) {
+ this.binding = binding;
+ this.stateListener = StateListener.noop();
+ }
+
+ @Override
+ public void set(T value) {
+ boolean changed = !this.get().equals(value);
+
+ this.binding.setValue(value);
+
+ if (changed) stateListener.onStateChange(this.get(), value);
+ }
+
+ @Override
+ public T get() {
+ return this.binding.getValue();
+ }
+
+ @Override
+ public void apply() {
+ // no-op, state is always applied
+ }
+
+ @Override
+ public void resetToDefault(ResetAction action) {
+ this.set(binding.defaultValue());
+ }
+
+ @Override
+ public void sync() {
+ // no-op, state is always synced
+ }
+
+ @Override
+ public boolean isSynced() {
+ return true;
+ }
+
+ @Override
+ public boolean isAlwaysSynced() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return binding.defaultValue().equals(this.get());
+ }
+
+ @Override
+ public void addListener(StateListener<T> stateListener) {
+ this.stateListener = this.stateListener.andThen(stateListener);
+ }
+
+ @Override
+ public Binding<T> getBinding() {
+ return binding;
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java
index 2bd2e10..3ad0caf 100644
--- a/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java
+++ b/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java
@@ -13,124 +13,57 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
-import java.util.function.BiConsumer;
-@ApiStatus.Internal
-public final class LabelOptionImpl implements LabelOption {
- private final Component label;
- private final Component name = Component.literal("Label Option");
- private final OptionDescription description;
- private final Component tooltip = Component.empty();
- private final LabelController labelController;
- private final Binding<Component> binding;
+public class LabelOptionImpl extends OptionImpl<Component> implements LabelOption {
+ public LabelOptionImpl(
+ @NotNull StateManager<Component> stateManager,
+ @NotNull Collection<OptionEventListener<Component>> optionEventListeners
+ ) {
+ super(
+ Component.literal("Label Option"),
+ OptionDescription::of,
+ LabelController::new,
+ stateManager,
+ true,
+ ImmutableSet.of(),
+ optionEventListeners
+ );
+ }
public LabelOptionImpl(Component label) {
- Validate.notNull(label, "`label` must not be null");
-
- this.label = label;
- this.labelController = new LabelController(this);
- this.binding = Binding.immutable(label);
- this.description = OptionDescription.createBuilder()
- .text(this.label)
- .build();
+ this(
+ StateManager.createImmutable(label),
+ ImmutableSet.of()
+ );
}
@Override
public @NotNull Component label() {
- return label;
- }
-
- @Override
- public @NotNull Component name() {
- return name;
- }
-
- @Override
- public @NotNull OptionDescription description() {
- return description;
- }
-
- @Override
- public @NotNull Component tooltip() {
- return tooltip;
- }
-
- @Override
- public @NotNull Controller<Component> controller() {
- return labelController;
- }
-
- @Override
- public @NotNull Binding<Component> binding() {
- return binding;
- }
-
- @Override
- public boolean available() {
- return true;
+ return stateManager().get();
}
@Override
public void setAvailable(boolean available) {
- throw new UnsupportedOperationException("Label options cannot be disabled.");
- }
-
- @Override
- public @NotNull ImmutableSet<OptionFlag> flags() {
- return ImmutableSet.of();
- }
-
- @Override
- public boolean changed() {
- return false;
- }
-
- @Override
- public @NotNull Component pendingValue() {
- return label;
- }
-
- @Override
- public void requestSet(@NotNull Component value) {
-
- }
-
- @Override
- public boolean applyValue() {
- return false;
- }
-
- @Override
- public void forgetPendingValue() {
-
- }
-
- @Override
- public void requestSetDefault() {
-
- }
-
- @Override
- public boolean isPendingValueDefault() {
- return true;
- }
-
- @Override
- public boolean canResetToDefault() {
- return false;
- }
-
- @Override
- public void addListener(BiConsumer<Option<Component>, Component> changedListener) {
-
+ throw new UnsupportedOperationException("Cannot change availability of label option");
}
@ApiStatus.Internal
- public static final class BuilderImpl implements Builder {
+ public static final class BuilderImpl implements LabelOption.Builder {
+ private StateManager<Component> stateManager;
private final List<Component> lines = new ArrayList<>();
@Override
- public Builder line(@NotNull Component line) {
+ public LabelOption.Builder state(@NotNull StateManager<Component> stateManager) {
+ Validate.notNull(stateManager, "`stateManager` must not be null");
+ Validate.isTrue(this.lines.isEmpty(), "Cannot set state manager if lines have already been defined");
+
+ this.stateManager = stateManager;
+ return this;
+ }
+
+ @Override
+ public LabelOption.Builder line(@NotNull Component line) {
+ Validate.isTrue(stateManager == null, ".line() is a helper to create a state manager for you at build. If you have defined a custom state manager, do not use .line()");
Validate.notNull(line, "`line` must not be null");
this.lines.add(line);
@@ -138,23 +71,30 @@ public final class LabelOptionImpl implements LabelOption {
}
@Override
- public Builder lines(@NotNull Collection<? extends Component> lines) {
+ public LabelOption.Builder lines(@NotNull Collection<? extends Component> lines) {
+ Validate.isTrue(stateManager == null, ".lines() is a helper to create a state manager for you at build. If you have defined a custom state manager, do not use .lines()");
+
this.lines.addAll(lines);
return this;
}
@Override
public LabelOption build() {
- MutableComponent text = Component.empty();
- Iterator<Component> iterator = lines.iterator();
- while (iterator.hasNext()) {
- text.append(iterator.next());
-
- if (iterator.hasNext())
- text.append("\n");
+ Validate.isTrue(stateManager != null || !lines.isEmpty(), "Cannot build label option without a state manager or lines");
+
+ if (!lines.isEmpty()) {
+ MutableComponent text = Component.empty();
+ Iterator<Component> iterator = lines.iterator();
+ while (iterator.hasNext()) {
+ text.append(iterator.next());
+
+ if (iterator.hasNext())
+ text.append("\n");
+ }
+ this.stateManager = StateManager.createSimple(new SelfContainedBinding<>(text));
}
- return new LabelOptionImpl(text);
+ return new LabelOptionImpl(this.stateManager, ImmutableSet.of());
}
}
}
diff --git a/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java b/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java
index 1cd5e55..dc4e7ff 100644
--- a/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java
+++ b/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java
@@ -49,6 +49,11 @@ public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> {
}
@Override
+ public @NotNull StateManager<T> stateManager() {
+ throw new UnsupportedOperationException("ListOptionEntryImpl does not support state managers");
+ }
+
+ @Override
public @NotNull Binding<T> binding() {
return binding;
}
@@ -109,6 +114,11 @@ public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> {
}
@Override
+ public void addEventListener(OptionEventListener<T> listener) {
+
+ }
+
+ @Override
public void addListener(BiConsumer<Option<T>, T> changedListener) {
}
@@ -138,7 +148,7 @@ public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> {
@Override
public void setValue(T newValue) {
value = newValue;
- group.callListeners(true);
+ group.triggerListener(OptionEventListener.Event.OTHER, true);
}
@Override
diff --git a/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java
index c77d55f..e9dbb70 100644
--- a/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java
+++ b/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java
@@ -21,7 +21,7 @@ import java.util.stream.Collectors;
public final class ListOptionImpl<T> implements ListOption<T> {
private final Component name;
private final OptionDescription description;
- private final Binding<List<T>> binding;
+ private final StateManager<List<T>> stateManager;
private final Supplier<T> initialValue;
private final List<ListOptionEntry<T>> entries;
private final boolean collapsed;
@@ -32,14 +32,14 @@ public final class ListOptionImpl<T> implements ListOption<T> {
private final ImmutableSet<OptionFlag> flags;
private final EntryFactory entryFactory;
- private final List<BiConsumer<Option<List<T>>, List<T>>> listeners;
+ private final List<OptionEventListener<List<T>>> listeners;
private final List<Runnable> refreshListeners;
- private int listenerTriggerDepth = 0;
+ private int currentListenerDepth = 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) {
+ public ListOptionImpl(@NotNull Component name, @NotNull OptionDescription description, @NotNull StateManager<List<T>> stateManager, @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<OptionEventListener<List<T>>> listeners) {
this.name = name;
this.description = description;
- this.binding = new SafeBinding<>(binding);
+ this.stateManager = stateManager;
this.initialValue = initialValue;
this.entryFactory = new EntryFactory(controllerFunction);
this.entries = createEntries(binding().getValue());
@@ -52,7 +52,10 @@ public final class ListOptionImpl<T> implements ListOption<T> {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
this.refreshListeners = new ArrayList<>();
- callListeners(true);
+
+ this.stateManager.addListener((oldValue, newValue) ->
+ triggerListener(OptionEventListener.Event.STATE_CHANGE, false));
+ triggerListener(OptionEventListener.Event.INITIAL, false);
}
@Override
@@ -81,8 +84,17 @@ public final class ListOptionImpl<T> implements ListOption<T> {
}
@Override
+ public @NotNull StateManager<List<T>> stateManager() {
+ return stateManager;
+ }
+
+ @Override
+ @Deprecated
public @NotNull Binding<List<T>> binding() {
- return binding;
+ if (stateManager instanceof ProvidesBindingForDeprecation) {
+ return ((ProvidesBindingForDeprecation<List<T>>) stateManager).getBinding();
+ }
+ throw new UnsupportedOperationException("Binding is not available for this option - using a new state manager which does not directly expose the binding as it may not have one.");
}
@Override
@@ -177,8 +189,12 @@ public final class ListOptionImpl<T> implements ListOption<T> {
this.available = available;
- if (changed)
- callListeners(false);
+ if (changed) {
+ if (!available) {
+ this.stateManager.sync();
+ }
+ this.triggerListener(OptionEventListener.Event.AVAILABILITY_CHANGE, !available);
+ }
}
@Override
@@ -195,8 +211,14 @@ public final class ListOptionImpl<T> implements ListOption<T> {
}
@Override
+ public void addEventListener(OptionEventListener<List<T>> listener) {
+ this.listeners.add(listener);
+ }
+
+ @Override
+ @Deprecated
public void addListener(BiConsumer<Option<List<T>>, List<T>> changedListener) {
- this.listeners.add(changedListener);
+ addEventListener((opt, event) -> changedListener.accept(opt, opt.pendingValue()));
}
@Override
@@ -213,30 +235,26 @@ public final class ListOptionImpl<T> implements ListOption<T> {
return values.stream().map(entryFactory::create).collect(Collectors.toList());
}
- void callListeners(boolean bypass) {
- List<T> pendingValue = 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!");
- }
+ void triggerListener(OptionEventListener.Event event, boolean allowDepth) {
+ if (allowDepth || currentListenerDepth == 0) {
+ Validate.isTrue(
+ currentListenerDepth <= 10,
+ "Listener depth exceeded 10! Possible cyclic listener pattern: a listener triggered an event that triggered the initial event etc etc."
+ );
- this.listenerTriggerDepth++;
+ currentListenerDepth++;
- 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);
- }
+ for (OptionEventListener<List<T>> listener : listeners) {
+ listener.onEvent(this, event);
}
- this.listenerTriggerDepth--;
+ currentListenerDepth--;
}
}
private void onRefresh() {
refreshListeners.forEach(Runnable::run);
- callListeners(true);
+ triggerListener(OptionEventListener.Event.OTHER, true);
}
private class EntryFactory {
@@ -256,7 +274,6 @@ public final class ListOptionImpl<T> implements ListOption<T> {
private Component name = Component.empty();
private OptionDescription description = OptionDescription.EMPTY;
private Function<ListOptionEntry<T>, Controller<T>> controllerFunction;
- private Binding<List<T>> binding = null;
private final Set<OptionFlag> flags = new HashSet<>();
private Supplier<T> initialValue;
private boolean collapsed = false;
@@ -264,7 +281,10 @@ public final class ListOptionImpl<T> implements ListOption<T> {
private int minimumNumberOfEntries = 0;
private int maximumNumberOfEntries = Integer.MAX_VALUE;
private boolean insertEntriesAtEnd = false;
- private final List<BiConsumer<Option<List<T>>, List<T>>> listeners = new ArrayList<>();
+ private final List<OptionEventListener<List<T>>> listeners = new ArrayList<>();
+
+ private Binding<List<T>> binding;
+ private StateManager<List<T>> stateManager;
@Override
public Builder<T> name(@NotNull Component name) {
@@ -315,8 +335,18 @@ public final class ListOptionImpl<T> implements ListOption<T> {
}
@Override
+ public Builder<T> state(@NotNull StateManager<List<T>> stateManager) {
+ Validate.notNull(stateManager, "`stateManager` cannot be null");
+ Validate.isTrue(binding == null, "Cannot set state manager if binding is already set");
+
+ this.stateManager = stateManager;
+ return this;
+ }
+
+ @Override
public Builder<T> binding(@NotNull Binding<List<T>> binding) {
Validate.notNull(binding, "`binding` cannot be null");
+ Validate.isTrue(stateManager == null, "Cannot set binding if state manager is already set");
this.binding = binding;
return this;
@@ -379,24 +409,52 @@ public final class ListOptionImpl<T> implements ListOption<T> {
}
@Override
- public Builder<T> listener(@NotNull BiConsumer<Option<List<T>>, List<T>> listener) {
+ public Builder<T> addListener(@NotNull OptionEventListener<List<T>> listener) {
+ Validate.notNull(listener, "`listener` must not be null");
+
this.listeners.add(listener);
return this;
}
@Override
+ public Builder<T> addListeners(@NotNull Collection<@NotNull OptionEventListener<List<T>>> optionEventListeners) {
+ Validate.notNull(optionEventListeners, "`optionEventListeners` must not be null");
+
+ this.listeners.addAll(optionEventListeners);
+ return this;
+ }
+
+ @Override
+ public Builder<T> listener(@NotNull BiConsumer<Option<List<T>>, List<T>> listener) {
+ Validate.notNull(listener, "`listener` must not be null");
+
+ return this.addListener((opt, event) -> listener.accept(opt, opt.pendingValue()));
+ }
+
+ @Override
public Builder<T> listeners(@NotNull Collection<BiConsumer<Option<List<T>>, List<T>>> listeners) {
- this.listeners.addAll(listeners);
+ Validate.notNull(listeners, "`listeners` must not be null");
+
+ this.addListeners(listeners.stream()
+ .map(listener ->
+ (OptionEventListener<List<T>>) (opt, event) ->
+ listener.accept(opt, opt.pendingValue())
+ ).toList()
+ );
return this;
}
@Override
public ListOption<T> build() {
Validate.notNull(controllerFunction, "`controller` must not be null");
- Validate.notNull(binding, "`binding` must not be null");
Validate.notNull(initialValue, "`initialValue` must not be null");
+ Validate.isTrue(stateManager != null || binding != null, "Either a state manager or binding must be set");
+
+ if (stateManager == null) {
+ stateManager = StateManager.createSimple(binding);
+ }
- return new ListOptionImpl<>(name, description, binding, initialValue, controllerFunction, ImmutableSet.copyOf(flags), collapsed, available, minimumNumberOfEntries, maximumNumberOfEntries, insertEntriesAtEnd, listeners);
+ return new ListOptionImpl<>(name, description, stateManager, initialValue, controllerFunction, ImmutableSet.copyOf(flags), collapsed, available, minimumNumberOfEntries, maximumNumberOfEntries, insertEntriesAtEnd, listeners);
}
}
}
diff --git a/src/main/java/dev/isxander/yacl3/impl/NotNullBinding.java b/src/main/java/dev/isxander/yacl3/impl/NotNullBinding.java
new file mode 100644
index 0000000..d367b06
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/NotNullBinding.java
@@ -0,0 +1,31 @@
+package dev.isxander.yacl3.impl;
+
+import dev.isxander.yacl3.api.Binding;
+import org.apache.commons.lang3.Validate;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Objects;
+
+public class NotNullBinding<T> implements Binding<T> {
+ private final Binding<T> binding;
+
+ public NotNullBinding(Binding<T> binding) {
+ this.binding = binding;
+ }
+
+ @Override
+ public @NotNull T getValue() {
+ return Validate.notNull(binding.getValue(), "Binding's value must not be null, please use Optionals if you want null behaviour.");
+ }
+
+ @Override
+ public void setValue(@NotNull T value) {
+ Validate.notNull(value, "Binding's value must not be set to null, please use Optionals if you want null behaviour.");
+ binding.setValue(value);
+ }
+
+ @Override
+ public @NotNull T defaultValue() {
+ return Validate.notNull(binding.defaultValue(), "Binding's default value must not be null, please use Optionals if you want null behaviour.");
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java
index afe9517..296c01f 100644
--- a/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java
+++ b/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java
@@ -9,6 +9,7 @@ import net.minecraft.network.chat.Component;
import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiConsumer;
@@ -17,40 +18,39 @@ import java.util.function.Function;
import java.util.function.Supplier;
@ApiStatus.Internal
-public final class OptionImpl<T> implements Option<T> {
+public class OptionImpl<T> implements Option<T> {
private final Component name;
private OptionDescription description;
private final Controller<T> controller;
- private final Binding<T> binding;
private boolean available;
private final ImmutableSet<OptionFlag> flags;
- private T pendingValue;
-
- private final List<BiConsumer<Option<T>, T>> listeners;
- private int listenerTriggerDepth = 0;
+ private final StateManager<T> stateManager;
+ private final List<OptionEventListener<T>> listeners;
+ private int currentListenerDepth;
public OptionImpl(
@NotNull Component name,
@NotNull Function<T, OptionDescription> descriptionFunction,
@NotNull Function<Option<T>, Controller<T>> controlGetter,
- @NotNull Binding<T> binding,
+ @NotNull StateManager<T> stateManager,
boolean available,
ImmutableSet<OptionFlag> flags,
- @NotNull Collection<BiConsumer<Option<T>, T>> listeners
+ @NotNull Collection<OptionEventListener<T>> listeners
) {
this.name = name;
- this.binding = new SafeBinding<>(binding);
this.available = available;
this.flags = flags;
this.listeners = new ArrayList<>(listeners);
- this.pendingValue = binding.getValue();
+ this.stateManager = stateManager;
this.controller = controlGetter.apply(this);
- addListener((opt, pending) -> description = descriptionFunction.apply(pending));
- triggerListeners(true);
+ this.stateManager.addListener((oldValue, newValue) ->
+ triggerListener(OptionEventListener.Event.STATE_CHANGE, false));
+ addEventListener((opt, event) -> description = descriptionFunction.apply(opt.pendingValue()));
+ triggerListener(OptionEventListener.Event.INITIAL, false);
}
@Override
@@ -74,8 +74,17 @@ public final class OptionImpl<T> implements Option<T> {
}
@Override
+ public @NotNull StateManager<T> stateManager() {
+ return stateManager;
+ }
+
+ @Override
+ @Deprecated
public @NotNull Binding<T> bindin