From 13c7ba45ff201423eb8dba8a40cfb66ebb531439 Mon Sep 17 00:00:00 2001 From: Xander Date: Tue, 25 Apr 2023 16:28:41 +0100 Subject: Architectury! (#61) --- .../dev/isxander/yacl/impl/ButtonOptionImpl.java | 218 +++++++++++++ .../dev/isxander/yacl/impl/ConfigCategoryImpl.java | 137 +++++++++ .../dev/isxander/yacl/impl/GenericBindingImpl.java | 35 +++ .../dev/isxander/yacl/impl/LabelOptionImpl.java | 154 ++++++++++ .../isxander/yacl/impl/ListOptionEntryImpl.java | 149 +++++++++ .../dev/isxander/yacl/impl/ListOptionImpl.java | 338 +++++++++++++++++++++ .../dev/isxander/yacl/impl/OptionGroupImpl.java | 129 ++++++++ .../java/dev/isxander/yacl/impl/OptionImpl.java | 303 ++++++++++++++++++ .../yacl/impl/PlaceholderCategoryImpl.java | 99 ++++++ .../yacl/impl/YetAnotherConfigLibImpl.java | 122 ++++++++ .../yacl/impl/utils/DimensionIntegerImpl.java | 115 +++++++ .../isxander/yacl/impl/utils/YACLConstants.java | 8 + 12 files changed, 1807 insertions(+) create mode 100644 common/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/LabelOptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/ListOptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/OptionImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java create mode 100644 common/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java (limited to 'common/src/main/java/dev/isxander/yacl/impl') diff --git a/common/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java new file mode 100644 index 0000000..11da99e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java @@ -0,0 +1,218 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl.api.*; +import dev.isxander.yacl.gui.YACLScreen; +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 org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +@ApiStatus.Internal +public final class ButtonOptionImpl implements ButtonOption { + private final Component name; + private final Component tooltip; + private final BiConsumer action; + private boolean available; + private final Controller> controller; + private final Binding> binding; + + public ButtonOptionImpl( + @NotNull Component name, + @Nullable Component tooltip, + @NotNull BiConsumer action, + boolean available, + @NotNull Function>> controlGetter + ) { + this.name = name; + this.tooltip = tooltip; + this.action = action; + this.available = available; + this.controller = controlGetter.apply(this); + this.binding = new EmptyBinderImpl(); + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public @NotNull Component tooltip() { + return tooltip; + } + + @Override + public BiConsumer action() { + return action; + } + + @Override + public boolean available() { + return available; + } + + @Override + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public @NotNull Controller> controller() { + return controller; + } + + @Override + public @NotNull Binding> binding() { + return binding; + } + + @Override + public @NotNull Class> typeClass() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull ImmutableSet flags() { + return ImmutableSet.of(); + } + + @Override + public boolean changed() { + return false; + } + + @Override + public @NotNull BiConsumer pendingValue() { + throw new UnsupportedOperationException(); + } + + @Override + public void requestSet(BiConsumer 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>, BiConsumer> changedListener) { + + } + + private static class EmptyBinderImpl implements Binding> { + @Override + public void setValue(BiConsumer value) { + + } + + @Override + public BiConsumer getValue() { + throw new UnsupportedOperationException(); + } + + @Override + public BiConsumer defaultValue() { + throw new UnsupportedOperationException(); + } + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component name; + private final List tooltipLines = new ArrayList<>(); + private boolean available = true; + private Function>> controlGetter; + private BiConsumer action; + + @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.notNull(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + @Override + public Builder action(@NotNull BiConsumer action) { + Validate.notNull(action, "`action` cannot be null"); + + this.action = action; + return this; + } + + @Override + @Deprecated + public Builder action(@NotNull Consumer 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 Builder controller(@NotNull Function>> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controlGetter = control; + return this; + } + + @Override + public ButtonOption build() { + Validate.notNull(name, "`name` must not be null when building `Option`"); + Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); + Validate.notNull(action, "`action` must not be null when building `Option`"); + + MutableComponent concatenatedTooltip = Component.empty(); + boolean first = true; + for (Component line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new ButtonOptionImpl(name, concatenatedTooltip, action, available, controlGetter); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java new file mode 100644 index 0000000..2d39eb9 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java @@ -0,0 +1,137 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.ListOption; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionGroup; +import dev.isxander.yacl.impl.utils.YACLConstants; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentContents; +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 groups; + private final Component tooltip; + + public ConfigCategoryImpl(Component name, ImmutableList groups, Component tooltip) { + this.name = name; + this.groups = groups; + this.tooltip = tooltip; + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public @NotNull ImmutableList 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> rootOptions = new ArrayList<>(); + private final List groups = new ArrayList<>(); + + private final List 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> 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 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 combinedGroups = new ArrayList<>(); + combinedGroups.add(new OptionGroupImpl(Component.empty(), Component.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() == ComponentContents.EMPTY) + continue; + + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java b/common/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java new file mode 100644 index 0000000..0d668c6 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/GenericBindingImpl.java @@ -0,0 +1,35 @@ +package dev.isxander.yacl.impl; + +import dev.isxander.yacl.api.Binding; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class GenericBindingImpl implements Binding { + private final T def; + private final Supplier getter; + private final Consumer setter; + + public GenericBindingImpl(T def, Supplier getter, Consumer 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/common/src/main/java/dev/isxander/yacl/impl/LabelOptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/LabelOptionImpl.java new file mode 100644 index 0000000..732a373 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/LabelOptionImpl.java @@ -0,0 +1,154 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl.api.*; +import dev.isxander.yacl.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 Component tooltip = Component.empty(); + private final LabelController labelController; + private final Binding binding; + + public LabelOptionImpl(Component label) { + this.label = label; + this.labelController = new LabelController(this); + this.binding = Binding.immutable(label); + } + + @Override + public @NotNull Component label() { + return label; + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public @NotNull Component tooltip() { + return tooltip; + } + + @Override + public @NotNull Controller controller() { + return labelController; + } + + @Override + public @NotNull Binding 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 Class typeClass() { + return Component.class; + } + + @Override + public @NotNull ImmutableSet flags() { + return ImmutableSet.of(); + } + + @Override + public boolean changed() { + return false; + } + + @Override + public @NotNull Component pendingValue() { + return label; + } + + @Override + public void requestSet(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, Component> changedListener) { + + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private final List 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 lines) { + this.lines.addAll(lines); + return this; + } + + @Override + public LabelOption build() { + MutableComponent text = Component.empty(); + Iterator iterator = lines.iterator(); + while (iterator.hasNext()) { + text.append(iterator.next()); + + if (iterator.hasNext()) + text.append("\n"); + } + + return new LabelOptionImpl(text); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java new file mode 100644 index 0000000..c15efe6 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java @@ -0,0 +1,149 @@ +package dev.isxander.yacl.impl; + +import dev.isxander.yacl.api.*; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.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 implements ListOptionEntry { + private final ListOptionImpl group; + + private T value; + + private final Binding binding; + private final Controller controller; + + ListOptionEntryImpl(ListOptionImpl group, T initialValue, @NotNull Function, Controller> controlGetter) { + this.group = group; + this.value = initialValue; + this.binding = new EntryBinding(); + this.controller = new EntryController<>(controlGetter.apply(this), this); + } + + @Override + public @NotNull Component name() { + return Component.empty(); + } + + @Override + public @NotNull Component tooltip() { + return Component.empty(); + } + + @Override + public @NotNull Controller controller() { + return controller; + } + + @Override + public @NotNull Binding binding() { + return binding; + } + + @Override + public boolean available() { + return parentGroup().available(); + } + + @Override + public void setAvailable(boolean available) { + + } + + @Override + public ListOption parentGroup() { + return group; + } + + @Override + public boolean changed() { + return false; + } + + @Override + public @NotNull T pendingValue() { + return value; + } + + @Override + public void requestSet(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, T> changedListener) { + + } + + /** + * Open in case mods need to find the real controller type. + */ + @ApiStatus.Internal + public record EntryController(Controller controller, ListOptionEntryImpl entry) implements Controller { + @Override + public Option option() { + return controller.option(); + } + + @Override + public Component formatValue() { + return controller.formatValue(); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new ListEntryWidget(screen, entry, controller.provideWidget(screen, widgetDimension)); + } + } + + private class EntryBinding implements Binding { + @Override + public void setValue(T newValue) { + value = newValue; + group.callListeners(); + } + + @Override + public T getValue() { + return value; + } + + @Override + public T defaultValue() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/impl/ListOptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ListOptionImpl.java new file mode 100644 index 0000000..f47493c --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/ListOptionImpl.java @@ -0,0 +1,338 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl.api.*; +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.*; +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 implements ListOption { + private final Component name; + private final Component tooltip; + private final Binding> binding; + private final T initialValue; + private final List> entries; + private final boolean collapsed; + private boolean available; + private final Class typeClass; + private final ImmutableSet flags; + private final EntryFactory entryFactory; + private final List>, List>> listeners; + private final List refreshListeners; + + public ListOptionImpl(@NotNull Component name, @NotNull Component tooltip, @NotNull Binding> binding, @NotNull T initialValue, @NotNull Class typeClass, @NotNull Function, Controller> controllerFunction, ImmutableSet flags, boolean collapsed, boolean available, Collection>, List>> listeners) { + this.name = name; + this.tooltip = tooltip; + this.binding = binding; + this.initialValue = initialValue; + this.entryFactory = new EntryFactory(controllerFunction); + this.entries = createEntries(binding().getValue()); + this.collapsed = collapsed; + this.typeClass = typeClass; + this.flags = flags; + this.available = available; + this.listeners = new ArrayList<>(); + this.listeners.addAll(listeners); + this.refreshListeners = new ArrayList<>(); + callListeners(); + } + + @Override + public @NotNull Component name() { + return this.name; + } + + @Override + public @NotNull Component tooltip() { + return this.tooltip; + } + + @Override + public @NotNull ImmutableList> options() { + return ImmutableList.copyOf(entries); + } + + @Override + public @NotNull Controller> controller() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Binding> binding() { + return binding; + } + + @Override + public @NotNull Class> typeClass() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Class elementTypeClass() { + return typeClass; + } + + @Override + public boolean collapsed() { + return collapsed; + } + + @Override + public @NotNull ImmutableSet flags() { + return flags; + } + + @Override + public @NotNull ImmutableList pendingValue() { + return ImmutableList.copyOf(entries.stream().map(Option::pendingValue).toList()); + } + + @Override + public void insertEntry(int index, ListOptionEntry entry) { + entries.add(index, (ListOptionEntry) entry); + onRefresh(); + } + + @Override + public ListOptionEntry insertNewEntryToTop() { + ListOptionEntry newEntry = entryFactory.create(initialValue); + 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(List 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) { + this.available = available; + } + + @Override + public void addListener(BiConsumer>, List> changedListener) { + this.listeners.add(changedListener); + } + + @Override + public void addRefreshListener(Runnable changedListener) { + this.refreshListeners.add(changedListener); + } + + @Override + public boolean isRoot() { + return false; + } + + private List> createEntries(Collection values) { + return values.stream().map(entryFactory::create).collect(Collectors.toList()); + } + + void callListeners() { + List pendingValue = pendingValue(); + this.listeners.forEach(listener -> listener.accept(this, pendingValue)); + } + + private void onRefresh() { + refreshListeners.forEach(Runnable::run); + callListeners(); + } + + private class EntryFactory { + private final Function, Controller> controllerFunction; + + private EntryFactory(Function, Controller> controllerFunction) { + this.controllerFunction = controllerFunction; + } + + public ListOptionEntry create(T initialValue) { + return new ListOptionEntryImpl<>(ListOptionImpl.this, initialValue, controllerFunction); + } + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component name = Component.empty(); + private final List tooltipLines = new ArrayList<>(); + private Function, Controller> controllerFunction; + private Binding> binding = null; + private final Set flags = new HashSet<>(); + private T initialValue; + private boolean collapsed = false; + private boolean available = true; + private final List>, List>> listeners = new ArrayList<>(); + private final Class typeClass; + + public BuilderImpl(Class typeClass) { + this.typeClass = typeClass; + } + + @Override + public Builder name(@NotNull Component name) { + Validate.notNull(name, "`name` must not 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 initial(@NotNull T initialValue) { + Validate.notNull(initialValue, "`initialValue` cannot be empty"); + + this.initialValue = initialValue; + return this; + } + + @Override + public Builder controller(@NotNull Function, Controller> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controllerFunction = control; + return this; + } + + @Override + public Builder binding(@NotNull Binding> binding) { + Validate.notNull(binding, "`binding` cannot be null"); + + this.binding = binding; + return this; + } + + @Override + public Builder binding(@NotNull List def, @NotNull Supplier<@NotNull List> getter, @NotNull Consumer<@NotNull List> 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 available(boolean available) { + this.available = available; + return this; + } + + @Override + public Builder flag(@NotNull OptionFlag... flag) { + Validate.notNull(flag, "`flag` must not be null"); + + this.flags.addAll(Arrays.asList(flag)); + return this; + } + + @Override + public Builder flags(@NotNull Collection flags) { + Validate.notNull(flags, "`flags` must not be null"); + + this.flags.addAll(flags); + return this; + } + + @Override + public Builder collapsed(boolean collapsible) { + this.collapsed = collapsible; + return this; + } + + @Override + public Builder listener(@NotNull BiConsumer>, List> listener) { + this.listeners.add(listener); + return this; + } + + @Override + public Builder listeners(@NotNull Collection>, List>> listeners) { + this.listeners.addAll(listeners); + return this; + } + + @Override + public ListOption 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"); + + MutableComponent concatenatedTooltip = Component.empty(); + boolean first = true; + for (Component line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new ListOptionImpl<>(name, concatenatedTooltip, binding, initialValue, typeClass, controllerFunction, ImmutableSet.copyOf(flags), collapsed, available, listeners); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java b/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java new file mode 100644 index 0000000..113aefc --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java @@ -0,0 +1,129 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.ListOption; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionGroup; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentContents; +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 OptionGroupImpl implements OptionGroup { + private final @NotNull Component name; + private final @NotNull Component tooltip; + private final ImmutableList> options; + private final boolean collapsed; + private final boolean isRoot; + + public OptionGroupImpl(@NotNull Component name, @NotNull Component tooltip, ImmutableList> options, boolean collapsed, boolean isRoot) { + this.name = name; + this.tooltip = tooltip; + this.options = options; + this.collapsed = collapsed; + this.isRoot = isRoot; + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public @NotNull Component tooltip() { + return tooltip; + } + + @Override + public @NotNull ImmutableList> 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 final List tooltipLines = new ArrayList<>(); + private final List> 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 tooltip(@NotNull Component... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + 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> 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`"); + + MutableComponent concatenatedTooltip = Component.empty(); + boolean first = true; + for (Component line : tooltipLines) { + if (line.getContents() == ComponentContents.EMPTY) + continue; + + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), collapsed, false); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/impl/OptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/OptionImpl.java new file mode 100644 index 0000000..4b65d56 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/OptionImpl.java @@ -0,0 +1,303 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl.api.Binding; +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionFlag; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentContents; +import net.minecraft.network.chat.MutableComponent; +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; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +@ApiStatus.Internal +public final class OptionImpl implements Option { + private final Component name; + private Component tooltip; + private final Controller controller; + private final Binding binding; + private boolean available; + + private final ImmutableSet flags; + + private final Class typeClass; + + private T pendingValue; + + private final List, T>> listeners; + + public OptionImpl( + @NotNull Component name, + @Nullable Function tooltipGetter, + @NotNull Function, Controller> controlGetter, + @NotNull Binding binding, + boolean available, + ImmutableSet flags, + @NotNull Class typeClass, + @NotNull Collection, T>> listeners + ) { + this.name = name; + this.binding = binding; + this.available = available; + this.flags = flags; + this.typeClass = typeClass; + this.listeners = new ArrayList<>(listeners); + this.controller = controlGetter.apply(this); + + addListener((opt, pending) -> tooltip = tooltipGetter.apply(pending)); + requestSet(binding().getValue()); + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public @NotNull Component tooltip() { + return tooltip; + } + + @Override + public @NotNull Controller controller() { + return controller; + } + + @Override + public @NotNull Binding binding() { + return binding; + } + + @Override + public boolean available() { + return available; + } + + @Override + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public @NotNull Class typeClass() { + return typeClass; + } + + @Override + public @NotNull ImmutableSet flags() { + return flags; + } + + @Override + public boolean changed() { + return !binding().getValue().equals(pendingValue); + } + + @Override + public @NotNull T pendingValue() { + return pendingValue; + } + + @Override + public void requestSet(T value) { + pendingValue = value; + listeners.forEach(listener -> listener.accept(this, 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 void addListener(BiConsumer, T> changedListener) { + this.listeners.add(changedListener); + } + + @ApiStatus.Internal + public static class BuilderImpl implements Builder { + private Component name = Component.literal("Name not specified!").withStyle(ChatFormatting.RED); + + private final List> tooltipGetters = new ArrayList<>(); + + private Function, Controller> controlGetter; + + private Binding binding; + + private boolean available = true; + + private boolean instant = false; + + private final Set flags = new HashSet<>(); + + private final Class typeClass; + + private final List, T>> listeners = new ArrayList<>(); + + public BuilderImpl(Class typeClass) { + this.typeClass = typeClass; + } + + @Override + public Builder name(@NotNull Component name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + public Builder tooltip(@NotNull Function tooltipGetter) { + Validate.notNull(tooltipGetter, "`tooltipGetter` cannot be null"); + + this.tooltipGetters.add(tooltipGetter); + return this; + } + + @Override + @SafeVarargs + @Deprecated + public final Builder tooltip(@NotNull Function... tooltipGetter) { + Validate.notNull(tooltipGetter, "`tooltipGetter` cannot be null"); + + this.tooltipGetters.addAll(List.of(tooltipGetter)); + return this; + } + + @Override + public Builder tooltip(@NotNull Component... tooltips) { + var tooltipFunctions = Arrays.stream(tooltips) + .map(t -> (Function) opt -> t) + .toList(); + + this.tooltipGetters.addAll(tooltipFunctions); + return this; + } + + @Override + public Builder controller(@NotNull Function, Controller> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controlGetter = control; + return this; + } + + @Override + public Builder binding(@NotNull Binding binding) { + Validate.notNull(binding, "`binding` cannot be null"); + + this.binding = binding; + return this; + } + + @Override + public Builder 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 available(boolean available) { + this.available = available; + return this; + } + + @Override + public Builder flag(@NotNull OptionFlag... flag) { + Validate.notNull(flag, "`flag` must not be null"); + + this.flags.addAll(Arrays.asList(flag)); + return this; + } + + @Override + public Builder flags(@NotNull Collection flags) { + Validate.notNull(flags, "`flags` must not be null"); + + this.flags.addAll(flags); + return this; + } + + @Override + public Builder instant(boolean instant) { + this.instant = instant; + return this; + } + + @Override + public Builder listener(@NotNull BiConsumer, T> listener) { + this.listeners.add(listener); + return this; + } + + @Override + public Builder listeners(@NotNull Collection, T>> listeners) { + this.listeners.addAll(listeners); + return this; + } + + @Override + public Option 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"); + + Function concatenatedTooltipGetter = value -> { + MutableComponent concatenatedTooltip = Component.empty(); + boolean first = true; + for (Function line : tooltipGetters) { + Component lineComponent = line.apply(value); + + if (lineComponent.getContents() == ComponentContents.EMPTY) + continue; + + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(lineComponent); + } + + return concatenatedTooltip; + }; + + if (instant) { + listeners.add((opt, pendingValue) -> opt.applyValue()); + } + + return new OptionImpl<>(name, concatenatedTooltipGetter, controlGetter, binding, available, ImmutableSet.copyOf(flags), typeClass, listeners); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java b/common/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java new file mode 100644 index 0000000..4e41a8f --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java @@ -0,0 +1,99 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.OptionGroup; +import dev.isxander.yacl.api.PlaceholderCategory; +import dev.isxander.yacl.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 screen; + private final Component tooltip; + + public PlaceholderCategoryImpl(Component name, BiFunction screen, Component tooltip) { + this.name = name; + this.screen = screen; + this.tooltip = tooltip; + } + + @Override + public @NotNull ImmutableList groups() { + return ImmutableList.of(); + } + + @Override + public @NotNull Component name() { + return name; + } + + @Override + public BiFunction 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 tooltipLines = new ArrayList<>(); + + private BiFunction 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 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/common/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java b/common/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java new file mode 100644 index 0000000..3c3cad2 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java @@ -0,0 +1,122 @@ +package dev.isxander.yacl.impl; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.PlaceholderCategory; +import dev.isxander.yacl.api.YetAnotherConfigLib; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.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 categories; + private final Runnable saveFunction; + private final Consumer initConsumer; + + private boolean generated = false; + + public YetAnotherConfigLibImpl(Component title, ImmutableList categories, Runnable saveFunction, Consumer 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 categories() { + return categories; + } + + @Override + public Runnable saveFunction() { + return saveFunction; + } + + @Override + public Consumer initConsumer() { + return initConsumer; + } + + @ApiStatus.Internal + public static final class BuilderImpl implements Builder { + private Component title; + private final List categories = new ArrayList<>(); + private Runnable saveFunction = () -> {}; + private Consumer 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 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 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/common/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java b/common/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java new file mode 100644 index 0000000..6c7508d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/utils/DimensionIntegerImpl.java @@ -0,0 +1,115 @@ +package dev.isxander.yacl.impl.utils; + +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.api.utils.MutableDimension; + +public class DimensionIntegerImpl implements MutableDimension { + 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 clone() { + return new DimensionIntegerImpl(x, y, width, height); + } + + @Override public MutableDimension setX(Integer x) { this.x = x; return this; } + @Override public MutableDimension setY(Integer y) { this.y = y; return this; } + @Override public MutableDimension setWidth(Integer width) { this.width = width; return this; } + @Override public MutableDimension setHeight(Integer height) { this.height = height; return this; } + + @Override + public Dimension withX(Integer x) { + return clone().setX(x); + } + + @Override + public Dimension withY(Integer y) { + return clone().setY(y); + } + + @Override + public Dimension withWidth(Integer width) { + return clone().setWidth(width); + } + + @Override + public Dimension withHeight(Integer height) { + return clone().setHeight(height); + } + + @Override + public MutableDimension move(Integer x, Integer y) { + this.x += x; + this.y += y; + return this; + } + + @Override + public MutableDimension expand(Integer width, Integer height) { + this.width += width; + this.height += height; + return this; + } + + @Override + public Dimension moved(Integer x, Integer y) { + return clone().move(x, y); + } + + @Override + public Dimension expanded(Integer width, Integer height) { + return clone().expand(width, height); + } +} diff --git a/common/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java b/common/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java new file mode 100644 index 0000000..3d382d4 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/impl/utils/YACLConstants.java @@ -0,0 +1,8 @@ +package dev.isxander.yacl.impl.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class YACLConstants { + public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib"); +} -- cgit