aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/dev/isxander/yacl3/impl
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/impl')
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java205
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java134
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/GenericBindingImpl.java35
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java109
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java160
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java154
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java402
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java133
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/OptionGroupImpl.java121
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/OptionImpl.java295
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/PlaceholderCategoryImpl.java99
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/SafeBinding.java29
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/YetAnotherConfigLibImpl.java122
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/AbstractControllerBuilderImpl.java12
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/BooleanControllerBuilderImpl.java57
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/ColorControllerBuilderImpl.java27
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/CyclingListControllerBuilderImpl.java41
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/DoubleFieldControllerBuilderImpl.java51
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/DoubleSliderControllerBuilderImpl.java44
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/DropdownStringControllerBuilderImpl.java49
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/EnumControllerBuilderImpl.java42
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/EnumDropdownControllerBuilderImpl.java27
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/FloatFieldControllerBuilderImpl.java51
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/FloatSliderControllerBuilderImpl.java44
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/IntegerFieldControllerBuilderImpl.java51
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/IntegerSliderControllerBuilderImpl.java44
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/ItemControllerBuilderImpl.java18
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/LongFieldControllerBuilderImpl.java51
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/LongSliderControllerBuilderImpl.java44
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/StringControllerBuilderImpl.java17
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/controller/TickBoxControllerBuilderImpl.java17
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/utils/DimensionIntegerImpl.java115
-rw-r--r--src/main/java/dev/isxander/yacl3/impl/utils/YACLConstants.java8
33 files changed, 2808 insertions, 0 deletions
diff --git a/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java
new file mode 100644
index 0000000..170b8e0
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/ButtonOptionImpl.java
@@ -0,0 +1,205 @@
+package dev.isxander.yacl3.impl;
+
+import com.google.common.collect.ImmutableSet;
+import dev.isxander.yacl3.api.*;
+import dev.isxander.yacl3.gui.YACLScreen;
+import dev.isxander.yacl3.gui.controllers.ActionController;
+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.function.BiConsumer;
+import java.util.function.Consumer;
+
+@ApiStatus.Internal
+public final class ButtonOptionImpl implements ButtonOption {
+ private final Component name;
+ private final OptionDescription description;
+ private final BiConsumer<YACLScreen, ButtonOption> action;
+ private boolean available;
+ private final Controller<BiConsumer<YACLScreen, ButtonOption>> controller;
+ private final Binding<BiConsumer<YACLScreen, ButtonOption>> binding;
+
+ public ButtonOptionImpl(
+ @NotNull Component name,
+ @Nullable OptionDescription description,
+ @NotNull BiConsumer<YACLScreen, ButtonOption> action,
+ @Nullable Component text,
+ boolean available
+ ) {
+ this.name = name;
+ this.description = description;
+ this.action = action;
+ this.available = available;
+ this.controller = text != null ? new ActionController(this, text) : new ActionController(this);
+ this.binding = new EmptyBinderImpl();
+ }
+
+ @Override
+ public @NotNull Component name() {
+ return name;
+ }
+
+ @Override
+ public @NotNull OptionDescription description() {
+ return description;
+ }
+
+ @Override
+ public @NotNull Component tooltip() {
+ return description().text();
+ }
+
+ @Override
+ public BiConsumer<YACLScreen, ButtonOption> action() {
+ return action;
+ }
+
+ @Override
+ public boolean available() {
+ return available;
+ }
+
+ @Override
+ public void setAvailable(boolean available) {
+ this.available = available;
+ }
+
+ @Override
+ public @NotNull Controller<BiConsumer<YACLScreen, ButtonOption>> controller() {
+ return controller;
+ }
+
+ @Override
+ public @NotNull Binding<BiConsumer<YACLScreen, ButtonOption>> binding() {
+ return binding;
+ }
+
+ @Override
+ public @NotNull ImmutableSet<OptionFlag> flags() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public boolean changed() {
+ return false;
+ }
+
+ @Override
+ public @NotNull BiConsumer<YACLScreen, ButtonOption> pendingValue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void requestSet(@NotNull BiConsumer<YACLScreen, ButtonOption> value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean applyValue() {
+ return false;
+ }
+
+ @Override
+ public void forgetPendingValue() {
+
+ }
+
+ @Override
+ public void requestSetDefault() {
+
+ }
+
+ @Override
+ public boolean isPendingValueDefault() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addListener(BiConsumer<Option<BiConsumer<YACLScreen, ButtonOption>>, BiConsumer<YACLScreen, ButtonOption>> changedListener) {
+
+ }
+
+ private static class EmptyBinderImpl implements Binding<BiConsumer<YACLScreen, ButtonOption>> {
+ @Override
+ public void setValue(BiConsumer<YACLScreen, ButtonOption> value) {
+
+ }
+
+ @Override
+ public BiConsumer<YACLScreen, ButtonOption> getValue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public BiConsumer<YACLScreen, ButtonOption> defaultValue() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @ApiStatus.Internal
+ public static final class BuilderImpl implements Builder {
+ private Component name;
+ private Component text = null;
+ private OptionDescription description = OptionDescription.EMPTY;
+ private boolean available = true;
+ private BiConsumer<YACLScreen, ButtonOption> action;
+
+ @Override
+ public Builder name(@NotNull Component name) {
+ Validate.notNull(name, "`name` cannot be null");
+
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Builder text(@NotNull Component text) {
+ Validate.notNull(text, "`text` cannot be null");
+
+ this.text = text;
+ return this;
+ }
+
+ @Override
+ public Builder description(@NotNull OptionDescription description) {
+ Validate.notNull(description, "`description` cannot be null");
+
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public Builder action(@NotNull BiConsumer<YACLScreen, ButtonOption> action) {
+ Validate.notNull(action, "`action` cannot be null");
+
+ this.action = action;
+ return this;
+ }
+
+ @Override
+ @Deprecated
+ public Builder action(@NotNull Consumer<YACLScreen> action) {
+ Validate.notNull(action, "`action` cannot be null");
+
+ this.action = (screen, button) -> action.accept(screen);
+ return this;
+ }
+
+ @Override
+ public Builder available(boolean available) {
+ this.available = available;
+ return this;
+ }
+
+ @Override
+ public ButtonOption build() {
+ Validate.notNull(name, "`name` must not be null when building `ButtonOption`");
+ Validate.notNull(action, "`action` must not be null when building `ButtonOption`");
+
+ return new ButtonOptionImpl(name, description, action, text, available);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java b/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java
new file mode 100644
index 0000000..400abf6
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java
@@ -0,0 +1,134 @@
+package dev.isxander.yacl3.impl;
+
+import com.google.common.collect.ImmutableList;
+import dev.isxander.yacl3.api.*;
+import dev.isxander.yacl3.impl.utils.YACLConstants;
+import net.minecraft.network.chat.CommonComponents;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import org.apache.commons.lang3.Validate;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@ApiStatus.Internal
+public final class ConfigCategoryImpl implements ConfigCategory {
+ private final Component name;
+ private final ImmutableList<OptionGroup> groups;
+ private final Component tooltip;
+
+ public ConfigCategoryImpl(Component name, ImmutableList<OptionGroup> groups, Component tooltip) {
+ this.name = name;
+ this.groups = groups;
+ this.tooltip = tooltip;
+ }
+
+ @Override
+ public @NotNull Component name() {
+ return name;
+ }
+
+ @Override
+ public @NotNull ImmutableList<OptionGroup> groups() {
+ return groups;
+ }
+
+ @Override
+ public @NotNull Component tooltip() {
+ return tooltip;
+ }
+
+ @ApiStatus.Internal
+ public static final class BuilderImpl implements Builder {
+ private Component name;
+
+ private final List<Option<?>> rootOptions = new ArrayList<>();
+ private final List<OptionGroup> groups = new ArrayList<>();
+
+ private final List<Component> tooltipLines = new ArrayList<>();
+
+ @Override
+ public Builder name(@NotNull Component name) {
+ Validate.notNull(name, "`name` cannot be null");
+
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Builder option(@NotNull Option<?> option) {
+ Validate.notNull(option, "`option` must not be null");
+
+ if (option instanceof ListOption<?> listOption) {
+ YACLConstants.LOGGER.warn("Adding list option as an option is not supported! Rerouting to group!");
+ return group(listOption);
+ }
+
+ this.rootOptions.add(option);
+ return this;
+ }
+
+ @Override
+ public Builder options(@NotNull Collection<? extends Option<?>> options) {
+ Validate.notNull(options, "`options` must not be null");
+
+ if (options.stream().anyMatch(ListOption.class::isInstance))
+ throw new UnsupportedOperationException("List options must not be added as an option but a group!");
+
+ this.rootOptions.addAll(options);
+ return this;
+ }
+
+ @Override
+ public Builder group(@NotNull OptionGroup group) {
+ Validate.notNull(group, "`group` must not be null");
+
+ this.groups.add(group);
+ return this;
+ }
+
+ @Override
+ public Builder groups(@NotNull Collection<OptionGroup> groups) {
+ Validate.notEmpty(groups, "`groups` must not be empty");
+
+ this.groups.addAll(groups);
+ return this;
+ }
+
+ @Override
+ public Builder tooltip(@NotNull Component... tooltips) {
+ Validate.notEmpty(tooltips, "`tooltips` cannot be empty");
+
+ tooltipLines.addAll(List.of(tooltips));
+ return this;
+ }
+
+ @Override
+ public ConfigCategory build() {
+ Validate.notNull(name, "`name` must not be null to build `ConfigCategory`");
+
+ List<OptionGroup> combinedGroups = new ArrayList<>();
+ combinedGroups.add(new OptionGroupImpl(CommonComponents.EMPTY, OptionDescription.EMPTY, ImmutableList.copyOf(rootOptions), false, true));
+ combinedGroups.addAll(groups);
+
+ Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`");
+
+ MutableComponent concatenatedTooltip = Component.empty();
+ boolean first = true;
+ for (Component line : tooltipLines) {
+ if (line.getContents() == CommonComponents.EMPTY.getContents())
+ continue;
+
+ if (!first) concatenatedTooltip.append("\n");
+ first = false;
+
+ concatenatedTooltip.append(line);
+ }
+
+ return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/GenericBindingImpl.java b/src/main/java/dev/isxander/yacl3/impl/GenericBindingImpl.java
new file mode 100644
index 0000000..972c891
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/GenericBindingImpl.java
@@ -0,0 +1,35 @@
+package dev.isxander.yacl3.impl;
+
+import dev.isxander.yacl3.api.Binding;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public final class GenericBindingImpl<T> implements Binding<T> {
+ private final T def;
+ private final Supplier<T> getter;
+ private final Consumer<T> setter;
+
+ public GenericBindingImpl(T def, Supplier<T> getter, Consumer<T> setting) {
+ this.def = def;
+ this.getter = getter;
+ this.setter = setting;
+ }
+
+
+ @Override
+ public void setValue(T value) {
+ setter.accept(value);
+ }
+
+ @Override
+ public T getValue() {
+ return getter.get();
+ }
+
+ @Override
+ public T defaultValue() {
+ return def;
+ }
+
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java b/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java
new file mode 100644
index 0000000..64588f2
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/HiddenNameListOptionEntry.java
@@ -0,0 +1,109 @@
+package dev.isxander.yacl3.impl;
+
+import com.google.common.collect.ImmutableSet;
+import dev.isxander.yacl3.api.*;
+import net.minecraft.network.chat.Component;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.BiConsumer;
+
+public class HiddenNameListOptionEntry<T> implements ListOptionEntry<T> {
+ private final ListOptionEntry<T> option;
+
+ public HiddenNameListOptionEntry(ListOptionEntry<T> option) {
+ this.option = option;
+ }
+
+ @Override
+ public @NotNull Component name() {
+ return Component.empty();
+ }
+
+ @Override
+ public @NotNull OptionDescription description() {
+ return option.description();
+ }
+
+ @Override
+ @Deprecated
+ public @NotNull Component tooltip() {
+ return option.tooltip();
+ }
+
+ @Override
+ public @NotNull Controller<T> controller() {
+ return option.controller();
+ }
+
+ @Override
+ public @NotNull Binding<T> binding() {
+ return option.binding();
+ }
+
+ @Override
+ public boolean available() {
+ return option.available();
+ }
+
+ @Override
+ public void setAvailable(boolean available) {
+ option.setAvailable(available);
+ }
+
+ @Override
+ public ListOption<T> parentGroup() {
+ return option.parentGroup();
+ }
+
+ @Override
+ public @NotNull ImmutableSet<OptionFlag> flags() {
+ return option.flags();
+ }
+
+ @Override
+ public boolean changed() {
+ return option.changed();
+ }
+
+ @Override
+ public @NotNull T pendingValue() {
+ return option.pendingValue();
+ }
+
+ @Override
+ public void requestSet(@NotNull T value) {
+ option.requestSet(value);
+ }
+
+ @Override
+ public boolean applyValue() {
+ return option.applyValue();
+ }
+
+ @Override
+ public void forgetPendingValue() {
+ option.forgetPendingValue();
+ }
+
+ @Override
+ public void requestSetDefault() {
+ option.requestSetDefault();
+ }
+
+ @Override
+ public boolean isPendingValueDefault() {
+ return option.isPendingValueDefault();
+ }
+
+ @Override
+ public boolean canResetToDefault() {
+ return option.canResetToDefault();
+ }
+
+ @Override
+ public void addListener(BiConsumer<Option<T>, T> changedListener) {
+ option.addListener(changedListener);
+ }
+
+
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java
new file mode 100644
index 0000000..2bd2e10
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/LabelOptionImpl.java
@@ -0,0 +1,160 @@
+package dev.isxander.yacl3.impl;
+
+import com.google.common.collect.ImmutableSet;
+import dev.isxander.yacl3.api.*;
+import dev.isxander.yacl3.gui.controllers.LabelController;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import org.apache.commons.lang3.Validate;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+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 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();
+ }
+
+ @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;
+ }
+
+ @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) {
+
+ }
+
+ @ApiStatus.Internal
+ public static final class BuilderImpl implements Builder {
+ private final List<Component> lines = new ArrayList<>();
+
+ @Override
+ public Builder line(@NotNull Component line) {
+ Validate.notNull(line, "`line` must not be null");
+
+ this.lines.add(line);
+ return this;
+ }
+
+ @Override
+ public Builder lines(@NotNull Collection<? extends Component> 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");
+ }
+
+ return new LabelOptionImpl(text);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java b/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java
new file mode 100644
index 0000000..1cd5e55
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java
@@ -0,0 +1,154 @@
+package dev.isxander.yacl3.impl;
+
+import dev.isxander.yacl3.api.*;
+import dev.isxander.yacl3.api.utils.Dimension;
+import dev.isxander.yacl3.gui.AbstractWidget;
+import dev.isxander.yacl3.gui.YACLScreen;
+import dev.isxander.yacl3.gui.controllers.ListEntryWidget;
+import net.minecraft.network.chat.Component;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+@ApiStatus.Internal
+public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> {
+ private final ListOptionImpl<T> group;
+
+ private T value;
+
+ private final Binding<T> binding;
+ private final Controller<T> controller;
+
+ ListOptionEntryImpl(ListOptionImpl<T> group, T initialValue, @NotNull Function<ListOptionEntry<T>, Controller<T>> controlGetter) {
+ this.group = group;
+ this.value = initialValue;
+ this.binding = new EntryBinding();
+ this.controller = new EntryController<>(controlGetter.apply(new HiddenNameListOptionEntry<>(this)), this);
+ }
+
+ @Override
+ public @NotNull Component name() {
+ return group.name();
+ }
+
+ @Override
+ public @NotNull OptionDescription description() {
+ return group.description();
+ }
+
+ @Override
+ public @NotNull Component tooltip() {
+ return group.tooltip();
+ }
+
+ @Override
+ public @NotNull Controller<T> controller() {
+ return controller;
+ }
+
+ @Override
+ public @NotNull Binding<T> binding() {
+ return binding;
+ }
+
+ @Override
+ public boolean available() {
+ return parentGroup().available();
+ }
+
+ @Override
+ public void setAvailable(boolean available) {
+
+ }
+
+ @Override
+ public ListOption<T> parentGroup() {
+ return group;
+ }
+
+ @Override
+ public boolean changed() {
+ return false;
+ }
+
+ @Override
+ public @NotNull T pendingValue() {
+ return value;
+ }
+
+ @Override
+ public void requestSet(@NotNull T value) {
+ binding.setValue(value);
+ }
+
+ @Override
+ public boolean applyValue() {
+ return false;
+ }
+
+ @Override
+ public void forgetPendingValue() {
+
+ }
+
+ @Override
+ public void requestSetDefault() {
+
+ }
+
+ @Override
+ public boolean isPendingValueDefault() {
+ return false;
+ }
+
+ @Override
+ public boolean canResetToDefault() {
+ return false;
+ }
+
+ @Override
+ public void addListener(BiConsumer<Option<T>, T> changedListener) {
+
+ }
+
+ /**
+ * Open in case mods need to find the real controller type.
+ */
+ @ApiStatus.Internal
+ public record EntryController<T>(Controller<T> controller, ListOptionEntryImpl<T> entry) implements Controller<T> {
+ @Override
+ public Option<T> option() {
+ return controller.option();
+ }
+
+ @Override
+ public Component formatValue() {
+ return controller.formatValue();
+ }
+
+ @Override
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ return new ListEntryWidget(screen, entry, controller.provideWidget(screen, widgetDimension));
+ }
+ }
+
+ private class EntryBinding implements Binding<T> {
+ @Override
+ public void setValue(T newValue) {
+ value = newValue;
+ group.callListeners(true);
+ }
+
+ @Override
+ public T getValue() {
+ return value;
+ }
+
+ @Override
+ public T defaultValue() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java
new file mode 100644
index 0000000..c77d55f
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java
@@ -0,0 +1,402 @@
+package dev.isxander.yacl3.impl;
+
+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;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+@ApiStatus.Internal
+public final class ListOptionImpl<T> implements ListOption<T> {
+ private final Component name;
+ private final OptionDescription description;
+ private final Binding<List<T>> binding;
+ private final Supplier<T> initialValue;
+ private final List<ListOptionEntry<T>> entries;
+ private final boolean collapsed;
+ private boolean available;
+ private final int minimumNumberOfEntries;
+ private final int maximumNumberOfEntries;
+ 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;
+ this.description = description;
+ this.binding = new SafeBinding<>(binding);
+ this.initialValue = initialValue;
+ this.entryFactory = new EntryFactory(controllerFunction);
+ this.entries = createEntries(binding().getValue());
+ this.collapsed = collapsed;
+ this.flags = flags;
+ this.available = available;
+ this.minimumNumberOfEntries = minimumNumberOfEntries;
+ this.maximumNumberOfEntries = maximumNumberOfEntries;
+ this.insertEntriesAtEnd = insertEntriesAtEnd;
+ this.listeners = new ArrayList<>();
+ this.listeners.addAll(listeners);
+ this.refreshListeners = new ArrayList<>();
+ callListeners(true);
+ }
+
+ @Override
+ public @NotNull Component name() {
+ return this.name;
+ }
+
+ @Override
+ public @NotNull OptionDescription description() {
+ return this.description;
+ }
+
+ @Override
+ public @NotNull Component tooltip() {
+ return description().text();
+ }
+
+ @Override
+ public @NotNull ImmutableList<ListOptionEntry<T>> options() {
+ return ImmutableList.copyOf(entries);
+ }
+
+ @Override
+ public @NotNull Controller<List<T>> controller() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public @NotNull Binding<List<T>> binding() {
+ return binding;
+ }
+
+ @Override
+ public boolean collapsed() {
+ return collapsed;
+ }
+
+ @Override
+ public @NotNull ImmutableSet<OptionFlag> flags() {
+ return flags;
+ }
+
+ @Override
+ public @NotNull ImmutableList<T> pendingValue() {
+ return ImmutableList.copyOf(entries.stream().map(Option::pendingValue).toList());
+ }
+
+ @Override
+ public void insertEntry(int index, ListOptionEntry<?> entry) {
+ entries.add(index, (ListOptionEntry<T>) entry);
+ onRefresh();
+ }
+
+ @Override
+ public ListOptionEntry<T> insertNewEntry() {
+ ListOptionEntry<T> newEntry = entryFactory.create(initialValue.get());
+ if (insertEntriesAtEnd) {
+ entries.add(newEntry);
+ } else {
+ // insert at top
+ entries.add(0, newEntry);
+ }
+ onRefresh();
+ return newEntry;
+ }
+
+ @Override
+ public void removeEntry(ListOptionEntry<?> entry) {
+ if (entries.remove(entry))
+ onRefresh();
+ }
+
+ @Override
+ public int indexOf(ListOptionEntry<?> entry) {
+ return entries.indexOf(entry);
+ }
+
+ @Override
+ public void requestSet(@NotNull List<T> value) {
+ entries.clear();
+ entries.addAll(createEntries(value));
+ onRefresh();
+ }
+
+ @Override
+ public boolean changed() {
+ return !binding().getValue().equals(pendingValue());
+ }
+
+ @Override
+ public boolean applyValue() {
+ if (changed()) {
+ binding().setValue(pendingValue());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void forgetPendingValue() {
+ requestSet(binding().getValue());
+ }
+
+ @Override
+ public void requestSetDefault() {
+ requestSet(binding().defaultValue());
+ }
+
+ @Override
+ public boolean isPendingValueDefault() {
+ return binding().defaultValue().equals(pendingValue());
+ }
+
+ @Override
+ public boolean available() {
+ return available;
+ }
+
+ @Override
+ public void setAvailable(boolean available) {
+ boolean changed = this.available != available;
+
+ this.available = available;
+
+ if (changed)
+ callListeners(false);
+ }
+
+ @Override
+ public int numberOfEntries() {
+ return this.entries.size();
+ }
+ @Override
+ public int maximumNumberOfEntries() {
+ return this.maximumNumberOfEntries;
+ }
+ @Override
+ public int minimumNumberOfEntries() {
+ return this.minimumNumberOfEntries;
+ }
+
+ @Override
+ public void addListener(BiConsumer<Option<List<T>>, List<T>> changedListener) {
+ this.listeners.add(changedListener);
+ }
+
+ @Override
+ public void addRefreshListener(Runnable changedListener) {
+ this.refreshListeners.add(changedListener);
+ }
+
+ @Override
+ public boolean isRoot() {
+ return false;
+ }
+
+ private List<ListOptionEntry<T>> createEntries(Collection<T> values) {
+ 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!");
+ }
+
+ 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(true);
+ }
+
+ private class EntryFactory {
+ private final Function<ListOptionEntry<T>, Controller<T>> controllerFunction;
+
+ private EntryFactory(Function<ListOptionEntry<T>, Controller<T>> controllerFunction) {
+ this.controllerFunction = controllerFunction;
+ }
+
+ public ListOptionEntry<T> create(T initialValue) {
+ return new ListOptionEntryImpl<>(ListOptionImpl.this, initialValue, controllerFunction);
+ }
+ }
+
+ @ApiStatus.Internal
+ public static final class BuilderImpl<T> implements Builder<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;
+ private boolean available = true;
+ 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<>();
+
+ @Override
+ public Builder<T> name(@NotNull Component name) {
+ Validate.notNull(name, "`name` must not be null");
+
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Builder<T> description(@NotNull OptionDescription description) {
+ Validate.notNull(description, "`description` must not be null");
+
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public Builder<T> initial(@NotNull Supplier<T> initialValue) {
+ Validate.notNull(initialValue, "`initialValue` cannot be empty");
+
+ this.initialValue = initialValue;
+ return this;
+ }
+
+ @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");
+
+ this.controllerFunction = opt -> controller.apply(opt).build();
+ return this;
+ }
+
+ @Override
+ public Builder<T> customController(@NotNull Function<ListOptionEntry<T>, Controller<T>> control) {
+ Validate.notNull(control, "`control` cannot be null");
+
+ this.controllerFunction = control;
+ return this;
+ }
+
+ @Override
+ public Builder<T> binding(@NotNull Binding<List<T>> binding) {
+ Validate.notNull(binding, "`binding` cannot be null");
+
+ this.binding = binding;
+ return this;
+ }
+
+ @Override
+ public Builder<T> binding(@NotNull List<T> def, @NotNull Supplier<@NotNull List<T>> getter, @NotNull Consumer<@NotNull List<T>> setter) {
+ Validate.notNull(def, "`def` must not be null");
+ Validate.notNull(getter, "`getter` must not be null");
+ Validate.notNull(setter, "`setter` must not be null");
+
+ this.binding = Binding.generic(def, getter, setter);
+ return this;
+ }
+
+ @Override
+ public Builder<T> available(boolean available) {
+ this.available = available;
+ return this;
+ }
+
+ @Override
+ public Builder<T> minimumNumberOfEntries(int number) {
+ this.minimumNumberOfEntries = number;
+ return this;
+ }
+
+ @Override
+ public Builder<T> maximumNumberOfEntries(int number) {
+ this.maximumNumberOfEntries = number;
+ return this;
+ }
+
+ @Override
+ public Builder<T> insertEntriesAtEnd(boolean insertAtEnd) {
+ this.insertEntriesAtEnd = insertAtEnd;
+ return this;
+ }
+
+ @Override
+ public Builder<T> flag(@NotNull OptionFlag... flag) {
+ Validate.notNull(flag, "`flag` must not be null");
+
+ this.flags.addAll(Arrays.asList(flag));
+ return this;
+ }
+
+ @Override
+ public Builder<T> flags(@NotNull Collection<OptionFlag> flags) {
+ Validate.notNull(flags, "`flags` must not be null");
+
+ this.flags.addAll(flags);
+ return this;
+ }
+
+ @Override
+ public Builder<T> collapsed(boolean collapsible) {
+ this.collapsed = collapsible;
+ return this;
+ }
+
+ @Override
+ public Builder<T> listener(@NotNull BiConsumer<Option<List<T>>, List<T>> listener) {
+ this.listeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Builder<T> listeners(@NotNull Collection<BiConsumer<Option<List<T>>, List<T>>> listeners) {
+ this.listeners.addAll(listeners);
+ 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");
+
+ return new ListOptionImpl<>(name, description, binding, initialValue, controllerFunction, ImmutableSet.copyOf(flags), collapsed, available, minimumNumberOfEntries, maximumNumberOfEntries, insertEntriesAtEnd, listeners);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java
new file mode 100644
index 0000000..67fa6a6
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java
@@ -0,0 +1,133 @@
+package dev.isxander.yacl3.impl;
+
+import dev.isxander.yacl3.api.OptionDescription;
+import dev.isxander.yacl3.gui.image.ImageRenderer;
+import dev.isxander.yacl3.gui.image.ImageRendererManager;
+import dev.isxander.yacl3.gui.image.impl.AnimatedDynamicTextureImage;
+import dev.isxander.yacl3.gui.image.impl.DynamicTextureImage;
+import dev.isxander.yacl3.gui.image.impl.ResourceTextureImage;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.resources.ResourceLocation;
+import org.apache.commons.lang3.Validate;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+public record OptionDescriptionImpl(Component text, CompletableFuture<Optional<ImageRenderer>> image) implements OptionDescription {
+ public static class BuilderImpl implements Builder {
+ private final List<Component> descriptionLines = new ArrayList<>();
+ private CompletableFuture<Optional<ImageRenderer>> image = CompletableFuture.completedFuture(Optional.empty());
+ private boolean imageUnset = true;
+
+ @Override
+ public Builder text(Component... description) {
+ this.descriptionLines.addAll(Arrays.asList(description));
+ return this;
+ }
+
+ @Override
+ public Builder text(Collection<? extends Component> lines) {
+ this.descriptionLines.addAll(lines);
+ return this;
+ }
+
+ @Override
+ public Builder image(ResourceLocation image, int width, int height) {
+ Validate.isTrue(imageUnset, "Image already set!");
+ Validate.isTrue(width > 0, "Width must be greater than 0!");
+ Validate.isTrue(height > 0, "Height must be greater than 0!");
+
+ this.image = ImageRendererManager.registerImage(image, ResourceTextureImage.createFactory(image, 0, 0, width, height, width, height)).thenApply(Optional::of);
+ imageUnset = false;
+ return this;
+ }
+
+ @Override
+ public Builder image(ResourceLocation image, float u, float v, int width, int height, int textureWidth, int textureHeight) {
+ Validate.isTrue(imageUnset, "Image already set!");
+ Validate.isTrue(width > 0, "Width must be greater than 0!");
+ Validate.isTrue(height > 0, "Height must be greater than 0!");
+
+ this.image = ImageRendererManager.registerImage(image, ResourceTextureImage.createFactory(image, u, v, width, height, textureWidth, textureHeight)).thenApply(Optional::of);
+ imageUnset = false;
+ return this;
+ }
+
+ @Override
+ public Builder image(Path path, ResourceLocation uniqueLocation) {
+ Validate.isTrue(imageUnset, "Image already set!");
+
+ this.image = ImageRendererManager.registerImage(uniqueLocation, DynamicTextureImage.fromPath(path, uniqueLocation)).thenApply(Optional::of);
+ imageUnset = false;
+ return this;
+ }
+
+ @Override
+ public Builder gifImage(ResourceLocation image) {
+ Validate.isTrue(imageUnset, "Image already set!");
+
+ this.image = ImageRendererManager.registerImage(image, AnimatedDynamicTextureImage.createGIFFromTexture(image)).thenApply(Optional::of);
+ imageUnset = false;
+ return this;
+ }
+
+ @Override
+ public Builder gifImage(Path path, ResourceLocation uniqueLocation) {
+ Validate.isTrue(imageUnset, "Image already set!");
+
+ this.image = ImageRendererManager.registerImage(uniqueLocation, AnimatedDynamicTextureImage.createGIFFromPath(path, uniqueLocation)).thenApply(Optional::of);
+ imageUnset = false;
+ return this;
+ }
+
+ @Override
+ public Builder webpImage(ResourceLocation image) {
+ Validate.isTrue(imageUnset, "Image already set!");
+
+ Optional<ImageRenderer> completedImage = ImageRendererManager.getImage(image);
+ if (completedImage.isPresent()) {
+ this.image = CompletableFuture.completedFuture(completedImage);
+ } else {
+ this.image = ImageRendererManager.registerImage(image, AnimatedDynamicTextureImage.createWEBPFromTexture(image)).thenApply(Optional::of);
+ }
+
+ imageUnset = false;
+ return this;
+ }
+
+ @Override
+ public Builder webpImage(Path path, ResourceLocation uniqueLocation) {
+ Validate.isTrue(imageUnset, "Image already set!");
+
+ this.image = ImageRendererManager.registerImage(uniqueLocation, AnimatedDynamicTextureImage.createWEBPFromPath(path, uniqueLocation)).thenApply(Optional::of);
+ imageUnset = false;
+ return this;
+ }
+
+ @Override
+ public Builder customImage(CompletableFuture<Optional<ImageRenderer>> image) {
+ Validate.notNull(image, "Image cannot be null!");
+ Validate.isTrue(imageUnset, "Image already set!");
+
+ this.image = image;
+ this.imageUnset = false;
+ return this;
+ }
+
+ @Override
+ public OptionDescription build() {
+ MutableComponent concatenatedDescription = Component.empty();
+ Iterator<Component> iter = descriptionLines.iterator();
+ while (iter.hasNext()) {
+ concatenatedDescription.append(iter.next());
+ if (iter.hasNext()) concatenatedDescription.append("\n");
+ }
+
+ return new OptionDescriptionImpl(concatenatedDescription, image);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/OptionGroupImpl.java b/src/main/java/dev/isxander/yacl3/impl/OptionGroupImpl.java
new file mode 100644
index 0000000..7805b29
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/OptionGroupImpl.java
@@ -0,0 +1,121 @@
+package dev.isxander.yacl3.impl;
+
+import com.google.common.collect.ImmutableList;
+import dev.isxander.yacl3.api.ListOption;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.OptionDescription;
+import dev.isxander.yacl3.api.OptionGroup;
+import net.minecraft.network.chat.Component;
+import org.apache.commons.lang3.Validate;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@ApiStatus.Internal
+public final class OptionGroupImpl implements OptionGroup {
+ private final @NotNull Component name;
+ private final @NotNull OptionDescription description;
+ private final ImmutableList<? extends Option<?>> options;
+ private final boolean collapsed;
+ private final boolean isRoot;
+
+ public OptionGroupImpl(@NotNull Component name, @NotNull OptionDescription description, ImmutableList<? extends Option<?>> options, boolean collapsed, boolean isRoot) {
+ this.name = name;
+ this.description = description;
+ this.options = options;
+ this.collapsed = collapsed;
+ this.isRoot = isRoot;
+ }
+
+ @Override
+ public @NotNull Component name() {
+ return name;
+ }
+
+ @Override
+ public OptionDescription description() {
+ return description;
+ }
+
+ @Override
+ public @NotNull Component tooltip() {
+ return description.text();
+ }
+
+ @Override
+ public @NotNull ImmutableList<? extends Option<?>> options() {
+ return options;
+ }
+
+ @Override
+ public boolean collapsed() {
+ return collapsed;
+ }
+
+ @Override
+ public boolean isRoot() {
+ return isRoot;
+ }
+
+ @ApiStatus.Internal
+ public static final class BuilderImpl implements Builder {
+ private Component name = Component.empty();
+ private OptionDescription description = OptionDescription.EMPTY;
+ private final List<Option<?>> options = new ArrayList<>();
+ private boolean collapsed = false;
+
+ @Override
+ public Builder name(@NotNull Component name) {
+ Validate.notNull(name, "`name` must not be null");
+
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Builder description(@NotNull OptionDescription description) {
+ Validate.notNull(description, "`description` must not be null");
+
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public Builder option(@NotNull Option<?> option) {
+ Validate.notNull(option, "`option` must not be null");
+
+ if (option instanceof ListOption<?>)
+ throw new UnsupportedOperationException("List options must not be added as an option but a group!");
+
+ this.options.add(option);
+ return this;
+ }
+
+ @Override
+ public Builder options(@NotNull Collection<? extends Option<?>> options) {
+ Validate.notEmpty(options, "`options` must not be empty");
+
+ if (options.stream().anyMatch(ListOption.class::isInstance))
+ throw new UnsupportedOperationException("List options must not be added as an option but a group!");
+
+ this.options.addAll(options);
+ return this;
+ }
+
+ @Override
+ public Builder collapsed(boolean collapsible) {
+ this.collapsed = collapsible;
+ return this;
+ }
+
+ @Override
+ public OptionGroup build() {
+ Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`");
+
+ return new OptionGroupImpl(name, description, ImmutableList.copyOf(options), collapsed, false);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java
new file mode 100644
index 0000000..afe9517
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java
@@ -0,0 +1,295 @@
+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;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@ApiStatus.Internal
+public final 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;
+
+ public OptionImpl(
+ @NotNull Component name,
+ @NotNull Function<T, OptionDescription> descriptionFunction,
+ @NotNull Function<Option<T>, Controller<T>> controlGetter,
+ @NotNull Binding<T> binding,
+ boolean available,
+ ImmutableSet<OptionFlag> flags,
+ @NotNull Collection<BiConsumer<Option<T>, 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.controller = controlGetter.apply(this);
+
+ addListener((opt, pending) -> description = descriptionFunction.apply(pending));
+ triggerListeners(true);
+ }
+
+ @Override
+ public @NotNull Component name() {
+ return name;
+ }
+
+ @Override
+ public @NotNull OptionDescription description() {
+ return this.description;
+ }
+
+ @Override
+ public @NotNull Component tooltip() {
+ return description.text();
+ }
+
+ @Override
+ public @NotNull Controller<T> controller() {
+ return controller;
+ }
+
+ @Override
+ public @NotNull Binding<T> binding() {
+ return binding;
+ }
+
+ @Override
+ public boolean available() {
+ return available;
+ }
+
+ @Override
+ public void setAvailable(boolean available) {
+ boolean changed = this.available != available;
+
+ this.available = available;
+
+ if (changed) {
+ if (!available) {
+ this.pendingValue = binding().getValue();
+ }
+ this.triggerListeners(!available);
+ }
+ }
+
+ @Override
+ public @NotNull ImmutableSet<OptionFlag> flags() {
+ return flags;
+ }
+
+ @Override
+ public boolean changed() {
+ return !binding().getValue().equals(pendingValue);
+ }
+
+ @Override
+ public @NotNull T pendingValue() {
+ return pendingValue;
+ }
+
+ @Override
+ public void requestSet(@NotNull T value) {
+ Validate.notNull(value, "`value` cannot be null");
+
+ pendingValue = value;
+ this.triggerListeners(true);
+ }
+
+ @Override
+ public boolean applyValue() {
+ if (changed()) {
+ binding().setValue(pendingValue);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void forgetPendingValue() {
+ requestSet(binding().getValue());
+ }
+
+ @Override
+ public void requestSetDefault() {
+ requestSet(binding().defaultValue());
+ }
+
+ @Override
+ public boolean isPendingValueDefault() {
+ return binding().defaultValue().equals(pendingValue());
+ }
+
+ @Override
+ public void addListener(BiConsumer<Option<T>, T> changedListener) {
+ 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);
+
+ private Function<T, OptionDescription> descriptionFunction = pending -> OptionDescription.EMPTY;
+
+ private Function<Option<T>, Controller<T>> controlGetter;
+
+ private Binding<T> binding;
+
+ private boolean available = true;
+
+ private boolean instant = false;
+
+ private final Set<OptionFlag> flags = new HashSet<>();
+
+ private final List<BiConsumer<Option<T>, T>> listeners = new ArrayList<>();
+
+ @Override
+ public Builder<T> name(@NotNull Component name) {
+ Validate.notNull(name, "`name` cannot be null");
+
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Builder<T> description(@NotNull OptionDescription description) {
+ return description(opt -> description);
+ }
+
+ @Override
+ public Builder<T> description(@NotNull Function<T, OptionDescription> descriptionFunction) {
+ this.descriptionFunction = descriptionFunction;
+ return this;
+ }
+
+ @Override
+ public Builder<T> controller(@NotNull Function<Option<T>, ControllerBuilder<T>> controllerBuilder) {
+ Validate.notNull(controllerBuilder, "`controllerBuilder` cannot be null");
+
+ return customController(opt -> controllerBuilder.apply(opt).build());
+ }
+
+ @Override
+ public Builder<T> customController(@NotNull Function<Option<T>, Controller<T>> control) {
+ Validate.notNull(control, "`control` cannot be null");
+
+ this.controlGetter = control;
+ return this;
+ }
+
+ @Override
+ public Builder<T> binding(@NotNull Binding<T> binding) {
+ Validate.notNull(binding, "`binding` cannot be null");
+
+ this.binding = binding;
+ return this;
+ }
+
+ @Override
+ public Builder<T> binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) {
+ Validate.notNull(def, "`def` must not be null");
+ Validate.notNull(getter, "`getter` must not be null");
+ Validate.notNull(setter, "`setter` must not be null");
+
+ this.binding = Binding.generic(def, getter, setter);
+ return this;
+ }
+
+ @Override
+ public Builder<T> available(boolean available) {
+ this.available = available;
+ return this;
+ }
+
+ @Override
+ public Builder<T> flag(@NotNull OptionFlag... flag) {
+ Validate.notNull(flag, "`flag` must not be null");
+
+ this.flags.addAll(Arrays.asList(flag));
+ return this;
+ }
+
+ @Override
+ public Builder<T> flags(@NotNull Collection<? extends OptionFlag> flags) {
+ Validate.notNull(flags, "`flags` must not be null");
+
+ this.flags.addAll(flags);
+ return this;
+ }
+
+ @Override
+ public Builder<T> instant(boolean instant) {
+ this.instant = instant;
+ return this;
+ }
+
+ @Override
+ public Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener) {
+ this.listeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Builder<T> listeners(@NotNull Collection<BiConsumer<Option<T>, T>> listeners) {
+ this.listeners.addAll(listeners);
+ return this;
+ }
+
+ @Override
+ public Option<T> build() {
+ Validate.notNull(controlGetter, "`control` must not be null when building `Option`");
+ Validate.notNull(binding, "`binding` must not be null when building `Option`");
+ Validate.isTrue(!instant || flags.isEmpty(), "instant application does not support option flags");
+
+ if (instant) {
+ listeners.add((opt, pendingValue) -> opt.applyValue());
+ }
+
+ return new OptionImpl<>(name, descriptionFunction, controlGetter, binding, available, ImmutableSet.copyOf(flags), listeners);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/PlaceholderCategoryImpl.java b/src/main/java/dev/isxander/yacl3/impl/PlaceholderCategoryImpl.java
new file mode 100644
index 0000000..5e836a3
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/PlaceholderCategoryImpl.java
@@ -0,0 +1,99 @@
+package dev.isxander.yacl3.impl;
+
+import com.google.common.collect.ImmutableList;
+import dev.isxander.yacl3.api.OptionGroup;
+import dev.isxander.yacl3.api.PlaceholderCategory;
+import dev.isxander.yacl3.gui.YACLScreen;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import org.apache.commons.lang3.Validate;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+@ApiStatus.Internal
+public final class PlaceholderCategoryImpl implements PlaceholderCategory {
+ private final Component name;
+ private final BiFunction<Minecraft, YACLScreen, Screen> screen;
+ private final Component tooltip;
+
+ public PlaceholderCategoryImpl(Component name, BiFunction<Minecraft, YACLScreen, Screen> screen, Component tooltip) {
+ this.name = name;
+ this.screen = screen;
+ this.tooltip = tooltip;
+ }
+
+ @Override
+ public @NotNull ImmutableList<OptionGroup> groups() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public @NotNull Component name() {
+ return name;
+ }
+
+ @Override
+ public BiFunction<Minecraft, YACLScreen, Screen> screen() {
+ return screen;
+ }
+
+ @Override
+ public @NotNull Component tooltip() {
+ return tooltip;
+ }
+
+ @ApiStatus.Internal
+ public static final class BuilderImpl implements Builder {
+ private Component name;
+
+ private final List<Component> tooltipLines = new ArrayList<>();
+
+ private BiFunction<Minecraft, YACLScreen, Screen> screenFunction;
+
+ @Override
+ public Builder name(@NotNull Component name) {
+ Validate.notNull(name, "`name` cannot be null");
+
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Builder tooltip(@NotNull Component... tooltips) {
+ Validate.notEmpty(tooltips, "`tooltips` cannot be empty");
+
+ tooltipLines.addAll(List.of(tooltips));
+ return this;
+ }
+
+ @Override
+ public Builder screen(@NotNull BiFunction<Minecraft, YACLScreen, Screen> screenFunction) {
+ Validate.notNull(screenFunction, "`screenFunction` cannot be null");
+
+ this.screenFunction = screenFunction;
+ return this;
+ }
+
+ @Override
+ public PlaceholderCategory build() {
+ Validate.notNull(name, "`name` must not be null to build `ConfigCategory`");
+
+ MutableComponent concatenatedTooltip = Component.empty();
+ boolean first = true;
+ for (Component line : tooltipLines) {
+ if (!first) concatenatedTooltip.append("\n");
+ first = false;
+
+ concatenatedTooltip.append(line);
+ }
+
+ return new PlaceholderCategoryImpl(name, screenFunction, concatenatedTooltip);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/SafeBinding.java b/src/main/java/dev/isxander/yacl3/impl/SafeBinding.java
new file mode 100644
index 0000000..c55d2be
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/SafeBinding.java
@@ -0,0 +1,29 @@
+package dev.isxander.yacl3.impl;
+
+import dev.isxander.yacl3.api.Binding;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Objects;
+
+public class SafeBinding<T> implements Binding<T> {
+ private final Binding<T> binding;
+
+ public SafeBinding(Binding<T> binding) {
+ this.binding = binding;
+ }
+
+ @Override
+ public @NotNull T getValue() {
+ return Objects.requireNonNull(binding.getValue());
+ }
+
+ @Override
+ public void setValue(@NotNull T value) {
+ binding.setValue(Objects.requireNonNull(value));
+ }
+
+ @Override
+ public @NotNull T defaultValue() {
+ return Objects.requireNonNull(binding.defaultValue());
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/YetAnotherConfigLibImpl.java b/src/main/java/dev/isxander/yacl3/impl/YetAnotherConfigLibImpl.java
new file mode 100644
index 0000000..0be02a7
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/YetAnotherConfigLibImpl.java
@@ -0,0 +1,122 @@
+package dev.isxander.yacl3.impl;
+
+import com.google.common.collect.ImmutableList;
+import dev.isxander.yacl3.api.ConfigCategory;
+import dev.isxander.yacl3.api.PlaceholderCategory;
+import dev.isxander.yacl3.api.YetAnotherConfigLib;
+import dev.isxander.yacl3.gui.YACLScreen;
+import dev.isxander.yacl3.impl.utils.YACLConstants;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.network.chat.Component;
+import org.apache.commons.lang3.Validate;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+
+@ApiStatus.Internal
+public final class YetAnotherConfigLibImpl implements YetAnotherConfigLib {
+ private final Component title;
+ private final ImmutableList<ConfigCategory> categories;
+ private final Runnable saveFunction;
+ private final Consumer<YACLScreen> initConsumer;
+
+ private boolean generated = false;
+
+ public YetAnotherConfigLibImpl(Component title, ImmutableList<ConfigCategory> categories, Runnable saveFunction, Consumer<YACLScreen> initConsumer) {
+ this.title = title;
+ this.categories = categories;
+ this.saveFunction = saveFunction;
+ this.initConsumer = initConsumer;
+ }
+
+ @Override
+ public Screen generateScreen(Screen parent) {
+ if (generated)
+ throw new UnsupportedOperationException("To prevent memory leaks, you should only generate a Screen once per instance. Please re-build the instance to generate another GUI.");
+
+ YACLConstants.LOGGER.info("Generating YACL screen");
+ generated = true;
+ return new YACLScreen(this, parent);
+ }
+
+ @Override
+ public Component title() {
+ return title;
+ }
+
+ @Override
+ public ImmutableList<ConfigCategory> categories() {
+ return categories;
+ }
+
+ @Override
+ public Runnable saveFunction() {
+ return saveFunction;
+ }
+
+ @Override
+ public Consumer<YACLScreen> initConsumer() {
+ return initConsumer;
+ }
+
+ @ApiStatus.Internal
+ public static final class BuilderImpl implements Builder {
+ private Component title;
+ private final List<ConfigCategory> categories = new ArrayList<>();
+ private Runnable saveFunction = () -> {};
+ private Consumer<YACLScreen> initConsumer = screen -> {};
+
+ @Override
+ public Builder title(@NotNull Component title) {
+ Validate.notNull(title, "`title` cannot be null");
+
+ this.title = title;
+ return this;
+ }
+
+ @Override
+ public Builder category(@NotNull ConfigCategory category) {
+ Validate.notNull(category, "`category` cannot be null");
+
+ this.categories.add(category);
+ return this;
+ }
+
+ @Override
+ public Builder categories(@NotNull Collection<? extends ConfigCategory> categories) {
+ Validate.notNull(categories, "`categories` cannot be null");
+
+ this.categories.addAll(categories);
+ return this;
+ }
+
+ @Override
+ public Builder save(@NotNull Runnable saveFunction) {
+ Validate.notNull(saveFunction, "`saveFunction` cannot be null");
+
+ this.saveFunction = saveFunction;
+ return this;
+ }
+
+ @Override
+ public Builder screenInit(@NotNull Consumer<YACLScreen> initConsumer) {
+ Validate.notNull(initConsumer, "`initConsumer` cannot be null");
+
+ this.initConsumer = initConsumer;
+ return this;
+ }
+
+ @Override
+ public YetAnotherConfigLib build() {
+ Validate.notNull(title, "`title must not be null to build `YetAnotherConfigLib`");
+ Validate.notEmpty(categories, "`categories` must not be empty to build `YetAnotherConfigLib`");
+ Validate.isTrue(!categories.stream().allMatch(category -> category instanceof PlaceholderCategory), "At least one regular category is required to build `YetAnotherConfigLib`");
+
+ return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories), saveFunction, initConsumer);
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/AbstractControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/AbstractControllerBuilderImpl.java
new file mode 100644
index 0000000..66c025a
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/AbstractControllerBuilderImpl.java
@@ -0,0 +1,12 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.ControllerBuilder;
+
+public abstract class AbstractControllerBuilderImpl<T> implements ControllerBuilder<T> {
+ protected final Option<T> option;
+
+ protected AbstractControllerBuilderImpl(Option<T> option) {
+ this.option = option;
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/BooleanControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/BooleanControllerBuilderImpl.java
new file mode 100644
index 0000000..063a177
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/BooleanControllerBuilderImpl.java
@@ -0,0 +1,57 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.BooleanControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.BooleanController;
+import net.minecraft.network.chat.Component;
+import org.apache.commons.lang3.Validate;
+
+import java.util.function.Function;
+
+public class BooleanControllerBuilderImpl extends AbstractControllerBuilderImpl<Boolean> implements BooleanControllerBuilder {
+ private boolean coloured = false;
+ private ValueFormatter<Boolean> formatter = BooleanController.ON_OFF_FORMATTER::apply;
+
+ public BooleanControllerBuilderImpl(Option<Boolean> option) {
+ super(option);
+ }
+
+ @Override
+ public BooleanControllerBuilder coloured(boolean coloured) {
+ this.coloured = coloured;
+ return this;
+ }
+
+ @Override
+ public BooleanControllerBuilder formatValue(ValueFormatter<Boolean> formatter) {
+ Validate.notNull(formatter, "formatter cannot be null");
+
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public BooleanControllerBuilder onOffFormatter() {
+ this.formatter = BooleanController.ON_OFF_FORMATTER::apply;
+ return this;
+ }
+
+ @Override
+ public BooleanControllerBuilder yesNoFormatter() {
+ this.formatter = BooleanController.YES_NO_FORMATTER::apply;
+ return this;
+ }
+
+ @Override
+ public BooleanControllerBuilder trueFalseFormatter() {
+ this.formatter = BooleanController.TRUE_FALSE_FORMATTER::apply;
+ return this;
+ }
+
+ @Override
+ public Controller<Boolean> build() {
+ return BooleanController.createInternal(option, formatter, coloured);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/ColorControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/ColorControllerBuilderImpl.java
new file mode 100644
index 0000000..9412165
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/ColorControllerBuilderImpl.java
@@ -0,0 +1,27 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.ColorControllerBuilder;
+import dev.isxander.yacl3.gui.controllers.ColorController;
+
+import java.awt.Color;
+
+public class ColorControllerBuilderImpl extends AbstractControllerBuilderImpl<Color> implements ColorControllerBuilder {
+ private boolean allowAlpha = false;
+
+ public ColorControllerBuilderImpl(Option<Color> option) {
+ super(option);
+ }
+
+ @Override
+ public ColorControllerBuilder allowAlpha(boolean allowAlpha) {
+ this.allowAlpha = allowAlpha;
+ return this;
+ }
+
+ @Override
+ public Controller<Color> build() {
+ return new ColorController(option, allowAlpha);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/CyclingListControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/CyclingListControllerBuilderImpl.java
new file mode 100644
index 0000000..8e2e481
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/CyclingListControllerBuilderImpl.java
@@ -0,0 +1,41 @@
+package dev.isxander.yacl3.impl.controller;
+
+import com.google.common.collect.ImmutableList;
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.CyclingListControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.cycling.CyclingListController;
+
+public final class CyclingListControllerBuilderImpl<T> extends AbstractControllerBuilderImpl<T> implements CyclingListControllerBuilder<T> {
+ private Iterable<? extends T> values;
+ private ValueFormatter<T> formatter = null;
+
+ public CyclingListControllerBuilderImpl(Option<T> option) {
+ super(option);
+ }
+
+ @Override
+ public CyclingListControllerBuilder<T> values(Iterable<? extends T> values) {
+ this.values = values;
+ return this;
+ }
+
+ @SafeVarargs
+ @Override
+ public final CyclingListControllerBuilder<T> values(T... values) {
+ this.values = ImmutableList.copyOf(values);
+ return this;
+ }
+
+ @Override
+ public CyclingListControllerBuilder<T> formatValue(ValueFormatter<T> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<T> build() {
+ return CyclingListController.createInternal(option, values, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/DoubleFieldControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/DoubleFieldControllerBuilderImpl.java
new file mode 100644
index 0000000..8d84e7d
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/DoubleFieldControllerBuilderImpl.java
@@ -0,0 +1,51 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.DoubleFieldControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.slider.DoubleSliderController;
+import dev.isxander.yacl3.gui.controllers.string.number.DoubleFieldController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class DoubleFieldControllerBuilderImpl extends AbstractControllerBuilderImpl<Double> implements DoubleFieldControllerBuilder {
+ private double min = Double.MIN_VALUE;
+ private double max = Double.MAX_VALUE;
+ private ValueFormatter<Double> formatter = DoubleSliderController.DEFAULT_FORMATTER::apply;
+
+ public DoubleFieldControllerBuilderImpl(Option<Double> option) {
+ super(option);
+ }
+
+ @Override
+ public DoubleFieldControllerBuilder min(Double min) {
+ this.min = min;
+ return this;
+ }
+
+ @Override
+ public DoubleFieldControllerBuilder max(Double max) {
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public DoubleFieldControllerBuilder range(Double min, Double max) {
+ this.min = min;
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public DoubleFieldControllerBuilder formatValue(ValueFormatter<Double> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<Double> build() {
+ return DoubleFieldController.createInternal(option, min, max, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/DoubleSliderControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/DoubleSliderControllerBuilderImpl.java
new file mode 100644
index 0000000..b696d57
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/DoubleSliderControllerBuilderImpl.java
@@ -0,0 +1,44 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.slider.DoubleSliderController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class DoubleSliderControllerBuilderImpl extends AbstractControllerBuilderImpl<Double> implements DoubleSliderControllerBuilder {
+ private double min, max;
+ private double step;
+ private ValueFormatter<Double> formatter = DoubleSliderController.DEFAULT_FORMATTER::apply;
+
+ public DoubleSliderControllerBuilderImpl(Option<Double> option) {
+ super(option);
+ }
+
+ @Override
+ public DoubleSliderControllerBuilder range(Double min, Double max) {
+ this.min = min;
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public DoubleSliderControllerBuilder step(Double step) {
+ this.step = step;
+ return this;
+ }
+
+ @Override
+ public DoubleSliderControllerBuilder formatValue(ValueFormatter<Double> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<Double> build() {
+ return DoubleSliderController.createInternal(option, min, max, step, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/DropdownStringControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/DropdownStringControllerBuilderImpl.java
new file mode 100644
index 0000000..b300a6a
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/DropdownStringControllerBuilderImpl.java
@@ -0,0 +1,49 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.DropdownStringControllerBuilder;
+import dev.isxander.yacl3.gui.controllers.dropdown.DropdownStringController;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DropdownStringControllerBuilderImpl extends StringControllerBuilderImpl implements DropdownStringControllerBuilder {
+ private List<String> values;
+ private boolean allowEmptyValue = false;
+ private boolean allowAnyValue = false;
+
+ public DropdownStringControllerBuilderImpl(Option<String> option) {
+ super(option);
+ }
+
+ @Override
+ public DropdownStringControllerBuilder values(List<String> values) {
+ this.values = values;
+ return this;
+ }
+
+ @Override
+ public DropdownStringControllerBuilderImpl values(String... values) {
+ this.values = Arrays.asList(values);
+ return this;
+ }
+
+ @Override
+ public DropdownStringControllerBuilderImpl allowEmptyValue(boolean allowEmptyValue) {
+ this.allowEmptyValue = allowEmptyValue;
+ return this;
+ }
+
+ @Override
+ public DropdownStringControllerBuilderImpl allowAnyValue(boolean allowAnyValue) {
+ this.allowAnyValue = allowAnyValue;
+ return this;
+ }
+
+ @Override
+ public Controller<String> build() {
+ return new DropdownStringController(option, values, allowEmptyValue, allowAnyValue);
+ }
+
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/EnumControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/EnumControllerBuilderImpl.java
new file mode 100644
index 0000000..04ee2a0
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/EnumControllerBuilderImpl.java
@@ -0,0 +1,42 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.EnumControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.cycling.EnumController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class EnumControllerBuilderImpl<T extends Enum<T>> extends AbstractControllerBuilderImpl<T> implements EnumControllerBuilder<T> {
+ private Class<T> enumClass;
+ private ValueFormatter<T> formatter = null;
+
+ public EnumControllerBuilderImpl(Option<T> option) {
+ super(option);
+ }
+
+ @Override
+ public EnumControllerBuilder<T> enumClass(Class<T> enumClass) {
+ this.enumClass = enumClass;
+ return this;
+ }
+
+ @Override
+ public EnumControllerBuilder<T> formatValue(ValueFormatter<T> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<T> build() {
+ ValueFormatter<T> formatter = this.formatter;
+ if (formatter == null) {
+ Function<T, Component> formatFunction = EnumController.getDefaultFormatter();
+ formatter = formatFunction::apply;
+ }
+
+ return EnumController.createInternal(option, formatter, enumClass.getEnumConstants());
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/EnumDropdownControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/EnumDropdownControllerBuilderImpl.java
new file mode 100644
index 0000000..4ac063f
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/EnumDropdownControllerBuilderImpl.java
@@ -0,0 +1,27 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.EnumDropdownControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.cycling.EnumController;
+import dev.isxander.yacl3.gui.controllers.dropdown.EnumDropdownController;
+
+public class EnumDropdownControllerBuilderImpl<E extends Enum<E>> extends AbstractControllerBuilderImpl<E> implements EnumDropdownControllerBuilder<E> {
+ private ValueFormatter<E> formatter = EnumController.<E>getDefaultFormatter()::apply;
+
+ public EnumDropdownControllerBuilderImpl(Option<E> option) {
+ super(option);
+ }
+
+ @Override
+ public EnumDropdownControllerBuilder<E> formatValue(ValueFormatter<E> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<E> build() {
+ return new EnumDropdownController<>(option, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/FloatFieldControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/FloatFieldControllerBuilderImpl.java
new file mode 100644
index 0000000..08fefd0
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/FloatFieldControllerBuilderImpl.java
@@ -0,0 +1,51 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.slider.FloatSliderController;
+import dev.isxander.yacl3.gui.controllers.string.number.FloatFieldController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class FloatFieldControllerBuilderImpl extends AbstractControllerBuilderImpl<Float> implements FloatFieldControllerBuilder {
+ private float min = Float.MIN_VALUE;
+ private float max = Float.MAX_VALUE;
+ private ValueFormatter<Float> formatter = FloatSliderController.DEFAULT_FORMATTER::apply;
+
+ public FloatFieldControllerBuilderImpl(Option<Float> option) {
+ super(option);
+ }
+
+ @Override
+ public FloatFieldControllerBuilder min(Float min) {
+ this.min = min;
+ return this;
+ }
+
+ @Override
+ public FloatFieldControllerBuilder max(Float max) {
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public FloatFieldControllerBuilder range(Float min, Float max) {
+ this.min = min;
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public FloatFieldControllerBuilder formatValue(ValueFormatter<Float> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<Float> build() {
+ return FloatFieldController.createInternal(option, min, max, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/FloatSliderControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/FloatSliderControllerBuilderImpl.java
new file mode 100644
index 0000000..9b2d75b
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/FloatSliderControllerBuilderImpl.java
@@ -0,0 +1,44 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.slider.FloatSliderController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class FloatSliderControllerBuilderImpl extends AbstractControllerBuilderImpl<Float> implements FloatSliderControllerBuilder {
+ private float min, max;
+ private float step;
+ private ValueFormatter<Float> formatter = FloatSliderController.DEFAULT_FORMATTER::apply;
+
+ public FloatSliderControllerBuilderImpl(Option<Float> option) {
+ super(option);
+ }
+
+ @Override
+ public FloatSliderControllerBuilder range(Float min, Float max) {
+ this.min = min;
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public FloatSliderControllerBuilder step(Float step) {
+ this.step = step;
+ return this;
+ }
+
+ @Override
+ public FloatSliderControllerBuilder formatValue(ValueFormatter<Float> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<Float> build() {
+ return FloatSliderController.createInternal(option, min, max, step, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/IntegerFieldControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/IntegerFieldControllerBuilderImpl.java
new file mode 100644
index 0000000..1435c49
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/IntegerFieldControllerBuilderImpl.java
@@ -0,0 +1,51 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.IntegerFieldControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.slider.IntegerSliderController;
+import dev.isxander.yacl3.gui.controllers.string.number.IntegerFieldController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class IntegerFieldControllerBuilderImpl extends AbstractControllerBuilderImpl<Integer> implements IntegerFieldControllerBuilder {
+ private int min = Integer.MIN_VALUE;
+ private int max = Integer.MAX_VALUE;
+ private ValueFormatter<Integer> formatter = IntegerSliderController.DEFAULT_FORMATTER::apply;
+
+ public IntegerFieldControllerBuilderImpl(Option<Integer> option) {
+ super(option);
+ }
+
+ @Override
+ public IntegerFieldControllerBuilder min(Integer min) {
+ this.min = min;
+ return this;
+ }
+
+ @Override
+ public IntegerFieldControllerBuilder max(Integer max) {
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public IntegerFieldControllerBuilder range(Integer min, Integer max) {
+ this.min = min;
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public IntegerFieldControllerBuilder formatValue(ValueFormatter<Integer> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<Integer> build() {
+ return IntegerFieldController.createInternal(option, min, max, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/IntegerSliderControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/IntegerSliderControllerBuilderImpl.java
new file mode 100644
index 0000000..b9395a0
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/IntegerSliderControllerBuilderImpl.java
@@ -0,0 +1,44 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.slider.IntegerSliderController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class IntegerSliderControllerBuilderImpl extends AbstractControllerBuilderImpl<Integer> implements IntegerSliderControllerBuilder {
+ private int min, max;
+ private int step;
+ private ValueFormatter<Integer> formatter = IntegerSliderController.DEFAULT_FORMATTER::apply;
+
+ public IntegerSliderControllerBuilderImpl(Option<Integer> option) {
+ super(option);
+ }
+
+ @Override
+ public IntegerSliderControllerBuilder range(Integer min, Integer max) {
+ this.min = min;
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public IntegerSliderControllerBuilder step(Integer step) {
+ this.step = step;
+ return this;
+ }
+
+ @Override
+ public IntegerSliderControllerBuilder formatValue(ValueFormatter<Integer> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<Integer> build() {
+ return IntegerSliderController.createInternal(option, min, max, step, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/ItemControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/ItemControllerBuilderImpl.java
new file mode 100644
index 0000000..9a817fb
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/ItemControllerBuilderImpl.java
@@ -0,0 +1,18 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.ItemControllerBuilder;
+import dev.isxander.yacl3.gui.controllers.dropdown.ItemController;
+import net.minecraft.world.item.Item;
+
+public class ItemControllerBuilderImpl extends AbstractControllerBuilderImpl<Item> implements ItemControllerBuilder {
+ public ItemControllerBuilderImpl(Option<Item> option) {
+ super(option);
+ }
+
+ @Override
+ public Controller<Item> build() {
+ return new ItemController(option);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/LongFieldControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/LongFieldControllerBuilderImpl.java
new file mode 100644
index 0000000..c7a3ea4
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/LongFieldControllerBuilderImpl.java
@@ -0,0 +1,51 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.LongFieldControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.slider.LongSliderController;
+import dev.isxander.yacl3.gui.controllers.string.number.LongFieldController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class LongFieldControllerBuilderImpl extends AbstractControllerBuilderImpl<Long> implements LongFieldControllerBuilder {
+ private long min = Long.MIN_VALUE;
+ private long max = Long.MAX_VALUE;
+ private ValueFormatter<Long> formatter = LongSliderController.DEFAULT_FORMATTER::apply;
+
+ public LongFieldControllerBuilderImpl(Option<Long> option) {
+ super(option);
+ }
+
+ @Override
+ public LongFieldControllerBuilder min(Long min) {
+ this.min = min;
+ return this;
+ }
+
+ @Override
+ public LongFieldControllerBuilder max(Long max) {
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public LongFieldControllerBuilder range(Long min, Long max) {
+ this.min = min;
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public LongFieldControllerBuilder formatValue(ValueFormatter<Long> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<Long> build() {
+ return LongFieldController.createInternal(option, min, max, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/LongSliderControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/LongSliderControllerBuilderImpl.java
new file mode 100644
index 0000000..5eda424
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/LongSliderControllerBuilderImpl.java
@@ -0,0 +1,44 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.LongSliderControllerBuilder;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.gui.controllers.slider.LongSliderController;
+import net.minecraft.network.chat.Component;
+
+import java.util.function.Function;
+
+public class LongSliderControllerBuilderImpl extends AbstractControllerBuilderImpl<Long> implements LongSliderControllerBuilder {
+ private long min, max;
+ private long step;
+ private ValueFormatter<Long> formatter = LongSliderController.DEFAULT_FORMATTER::apply;
+
+ public LongSliderControllerBuilderImpl(Option<Long> option) {
+ super(option);
+ }
+
+ @Override
+ public LongSliderControllerBuilder range(Long min, Long max) {
+ this.min = min;
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public LongSliderControllerBuilder step(Long step) {
+ this.step = step;
+ return this;
+ }
+
+ @Override
+ public LongSliderControllerBuilder formatValue(ValueFormatter<Long> formatter) {
+ this.formatter = formatter;
+ return this;
+ }
+
+ @Override
+ public Controller<Long> build() {
+ return LongSliderController.createInternal(option, min, max, step, formatter);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/StringControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/StringControllerBuilderImpl.java
new file mode 100644
index 0000000..a0f51b9
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/StringControllerBuilderImpl.java
@@ -0,0 +1,17 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.StringControllerBuilder;
+import dev.isxander.yacl3.gui.controllers.string.StringController;
+
+public class StringControllerBuilderImpl extends AbstractControllerBuilderImpl<String> implements StringControllerBuilder {
+ public StringControllerBuilderImpl(Option<String> option) {
+ super(option);
+ }
+
+ @Override
+ public Controller<String> build() {
+ return new StringController(option);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/controller/TickBoxControllerBuilderImpl.java b/src/main/java/dev/isxander/yacl3/impl/controller/TickBoxControllerBuilderImpl.java
new file mode 100644
index 0000000..3b29719
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/controller/TickBoxControllerBuilderImpl.java
@@ -0,0 +1,17 @@
+package dev.isxander.yacl3.impl.controller;
+
+import dev.isxander.yacl3.api.Controller;
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder;
+import dev.isxander.yacl3.gui.controllers.TickBoxController;
+
+public class TickBoxControllerBuilderImpl extends AbstractControllerBuilderImpl<Boolean> implements TickBoxControllerBuilder {
+ public TickBoxControllerBuilderImpl(Option<Boolean> option) {
+ super(option);
+ }
+
+ @Override
+ public Controller<Boolean> build() {
+ return new TickBoxController(option);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/utils/DimensionIntegerImpl.java b/src/main/java/dev/isxander/yacl3/impl/utils/DimensionIntegerImpl.java
new file mode 100644
index 0000000..7d29bbc
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/utils/DimensionIntegerImpl.java
@@ -0,0 +1,115 @@
+package dev.isxander.yacl3.impl.utils;
+
+import dev.isxander.yacl3.api.utils.Dimension;
+import dev.isxander.yacl3.api.utils.MutableDimension;
+
+public class DimensionIntegerImpl implements MutableDimension<Integer> {
+ private int x, y;
+ private int width, height;
+
+ public DimensionIntegerImpl(int x, int y, int width, int height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public Integer x() {
+ return x;
+ }
+
+ @Override
+ public Integer y() {
+ return y;
+ }
+
+ @Override
+ public Integer width() {
+ return width;
+ }
+
+ @Override
+ public Integer height() {
+ return height;
+ }
+
+ @Override
+ public Integer xLimit() {
+ return x + width;
+ }
+
+ @Override
+ public Integer yLimit() {
+ return y + height;
+ }
+
+ @Override
+ public Integer centerX() {
+ return x + width / 2;
+ }
+
+ @Override
+ public Integer centerY() {
+ return y + height / 2;
+ }
+
+ @Override
+ public boolean isPointInside(Integer x, Integer y) {
+ return x >= x() && x <= xLimit() && y >= y() && y <= yLimit();
+ }
+
+ @Override
+ public MutableDimension<Integer> clone() {
+ return new DimensionIntegerImpl(x, y, width, height);
+ }
+
+ @Override public MutableDimension<Integer> setX(Integer x) { this.x = x; return this; }
+ @Override public MutableDimension<Integer> setY(Integer y) { this.y = y; return this; }
+ @Override public MutableDimension<Integer> setWidth(Integer width) { this.width = width; return this; }
+ @Override public MutableDimension<Integer> setHeight(Integer height) { this.height = height; return this; }
+
+ @Override
+ public Dimension<Integer> withX(Integer x) {
+ return clone().setX(x);
+ }
+
+ @Override
+ public Dimension<Integer> withY(Integer y) {
+ return clone().setY(y);
+ }
+
+ @Override
+ public Dimension<Integer> withWidth(Integer width) {
+ return clone().setWidth(width);
+ }
+
+ @Override
+ public Dimension<Integer> withHeight(Integer height) {
+ return clone().setHeight(height);
+ }
+
+ @Override
+ public MutableDimension<Integer> move(Integer x, Integer y) {
+ this.x += x;
+ this.y += y;
+ return this;
+ }
+
+ @Override
+ public MutableDimension<Integer> expand(Integer width, Integer height) {
+ this.width += width;
+ this.height += height;
+ return this;
+ }
+
+ @Override
+ public Dimension<Integer> moved(Integer x, Integer y) {
+ return clone().move(x, y);
+ }
+
+ @Override
+ public Dimension<Integer> expanded(Integer width, Integer height) {
+ return clone().expand(width, height);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/impl/utils/YACLConstants.java b/src/main/java/dev/isxander/yacl3/impl/utils/YACLConstants.java
new file mode 100644
index 0000000..5ff1b79
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/impl/utils/YACLConstants.java
@@ -0,0 +1,8 @@
+package dev.isxander.yacl3.impl.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class YACLConstants {
+ public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib");
+}