diff options
author | isXander <xandersmith2008@gmail.com> | 2022-12-14 18:53:30 +0000 |
---|---|---|
committer | isXander <xandersmith2008@gmail.com> | 2022-12-14 18:53:30 +0000 |
commit | 51f4ae2d8e0a6cdcc7d50a037143f48a6132214a (patch) | |
tree | 9a48b8faf7c419cf9295e79b80e11e27fe8671b2 /src/client/java/dev/isxander | |
parent | 3d1f7eb6c149c14ef9eea98d2d8caa6768f8c51c (diff) | |
download | YetAnotherConfigLib-51f4ae2d8e0a6cdcc7d50a037143f48a6132214a.tar.gz YetAnotherConfigLib-51f4ae2d8e0a6cdcc7d50a037143f48a6132214a.tar.bz2 YetAnotherConfigLib-51f4ae2d8e0a6cdcc7d50a037143f48a6132214a.zip |
lots of minor fixes with lists and abstract builders
Diffstat (limited to 'src/client/java/dev/isxander')
19 files changed, 774 insertions, 560 deletions
diff --git a/src/client/java/dev/isxander/yacl/api/ButtonOption.java b/src/client/java/dev/isxander/yacl/api/ButtonOption.java index 1124a9a..2025840 100644 --- a/src/client/java/dev/isxander/yacl/api/ButtonOption.java +++ b/src/client/java/dev/isxander/yacl/api/ButtonOption.java @@ -20,31 +20,16 @@ public interface ButtonOption extends Option<BiConsumer<YACLScreen, ButtonOption BiConsumer<YACLScreen, ButtonOption> action(); static Builder createBuilder() { - return new Builder(); + return new ButtonOptionImpl.BuilderImpl(); } - class Builder { - private Text name; - private final List<Text> tooltipLines = new ArrayList<>(); - private boolean available = true; - private Function<ButtonOption, Controller<BiConsumer<YACLScreen, ButtonOption>>> controlGetter; - private BiConsumer<YACLScreen, ButtonOption> action; - - private Builder() { - - } - + interface Builder { /** * Sets the name to be used by the option. * * @see Option#name() */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } + Builder name(@NotNull Text name); /** * Sets the tooltip to be used by the option. @@ -53,19 +38,9 @@ public interface ButtonOption extends Option<BiConsumer<YACLScreen, ButtonOption * * @param tooltips text lines - merged with a new-line on {@link Option.Builder#build()}. */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notNull(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } - - public Builder action(@NotNull BiConsumer<YACLScreen, ButtonOption> action) { - Validate.notNull(action, "`action` cannot be null"); + Builder tooltip(@NotNull Text... tooltips); - this.action = action; - return this; - } + Builder action(@NotNull BiConsumer<YACLScreen, ButtonOption> action); /** * Action to be executed upon button press @@ -73,22 +48,14 @@ public interface ButtonOption extends Option<BiConsumer<YACLScreen, ButtonOption * @see ButtonOption#action() */ @Deprecated - public Builder action(@NotNull Consumer<YACLScreen> action) { - Validate.notNull(action, "`action` cannot be null"); - - this.action = (screen, button) -> action.accept(screen); - return this; - } + Builder action(@NotNull Consumer<YACLScreen> action); /** * Sets if the option can be configured * * @see Option#available() */ - public Builder available(boolean available) { - this.available = available; - return this; - } + Builder available(boolean available); /** * Sets the controller for the option. @@ -96,28 +63,8 @@ public interface ButtonOption extends Option<BiConsumer<YACLScreen, ButtonOption * * @see dev.isxander.yacl.gui.controllers */ - public Builder controller(@NotNull Function<ButtonOption, Controller<BiConsumer<YACLScreen, ButtonOption>>> control) { - Validate.notNull(control, "`control` cannot be null"); - - this.controlGetter = control; - return this; - } - - 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`"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } + Builder controller(@NotNull Function<ButtonOption, Controller<BiConsumer<YACLScreen, ButtonOption>>> control); - return new ButtonOptionImpl(name, concatenatedTooltip, action, available, controlGetter); - } + ButtonOption build(); } } diff --git a/src/client/java/dev/isxander/yacl/api/ConfigCategory.java b/src/client/java/dev/isxander/yacl/api/ConfigCategory.java index 19c7f72..eecb9cb 100644 --- a/src/client/java/dev/isxander/yacl/api/ConfigCategory.java +++ b/src/client/java/dev/isxander/yacl/api/ConfigCategory.java @@ -39,32 +39,16 @@ public interface ConfigCategory { * Creates a builder to construct a {@link ConfigCategory} */ static Builder createBuilder() { - return new Builder(); + return new ConfigCategoryImpl.BuilderImpl(); } - class Builder { - private Text name; - - private final List<Option<?>> rootOptions = new ArrayList<>(); - private final List<OptionGroup> groups = new ArrayList<>(); - - private final List<Text> tooltipLines = new ArrayList<>(); - - private Builder() { - - } - + interface Builder { /** * Sets name of the category * * @see ConfigCategory#name() */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } + Builder name(@NotNull Text name); /** * Adds an option to the root group of the category. @@ -74,17 +58,7 @@ public interface ConfigCategory { * @see ConfigCategory#groups() * @see OptionGroup#isRoot() */ - 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; - } + Builder option(@NotNull Option<?> option); /** * Adds multiple options to the root group of the category. @@ -94,39 +68,21 @@ public interface ConfigCategory { * @see ConfigCategory#groups() * @see OptionGroup#isRoot() */ - public Builder options(@NotNull Collection<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; - } + Builder options(@NotNull Collection<Option<?>> options); /** * Adds an option group. * To add an option to the root group, use {@link Builder#option(Option)} * To construct a group, use {@link OptionGroup#createBuilder()} */ - public Builder group(@NotNull OptionGroup group) { - Validate.notNull(group, "`group` must not be null"); - - this.groups.add(group); - return this; - } + Builder group(@NotNull OptionGroup group); /** * Adds multiple option groups. * To add multiple options to the root group, use {@link Builder#options(Collection)} * To construct a group, use {@link OptionGroup#createBuilder()} */ - public Builder groups(@NotNull Collection<OptionGroup> groups) { - Validate.notEmpty(groups, "`groups` must not be empty"); - - this.groups.addAll(groups); - return this; - } + Builder groups(@NotNull Collection<OptionGroup> groups); /** * Sets the tooltip to be used by the category. @@ -135,32 +91,8 @@ public interface ConfigCategory { * * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } - - public ConfigCategory build() { - Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); - - List<OptionGroup> combinedGroups = new ArrayList<>(); - combinedGroups.add(new OptionGroupImpl(Text.empty(), Text.empty(), ImmutableList.copyOf(rootOptions), false, true)); - combinedGroups.addAll(groups); - - Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } + Builder tooltip(@NotNull Text... tooltips); - return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip); - } + ConfigCategory build(); } } diff --git a/src/client/java/dev/isxander/yacl/api/ListOption.java b/src/client/java/dev/isxander/yacl/api/ListOption.java index 895898c..54ed3a5 100644 --- a/src/client/java/dev/isxander/yacl/api/ListOption.java +++ b/src/client/java/dev/isxander/yacl/api/ListOption.java @@ -51,36 +51,17 @@ public interface ListOption<T> extends OptionGroup, Option<List<T>> { void addRefreshListener(Runnable changedListener); static <T> Builder<T> createBuilder(Class<T> typeClass) { - return new Builder<>(typeClass); + return new ListOptionImpl.BuilderImpl<>(typeClass); } - class Builder<T> { - private Text name = Text.empty(); - private final List<Text> tooltipLines = new ArrayList<>(); - private Function<ListOptionEntry<T>, Controller<T>> controllerFunction; - private Binding<List<T>> binding = null; - private final Set<OptionFlag> flags = new HashSet<>(); - private T initialValue; - private boolean collapsed = false; - private boolean available = true; - private final Class<T> typeClass; - - private Builder(Class<T> typeClass) { - this.typeClass = typeClass; - } - + interface Builder<T> { /** * Sets name of the list, for UX purposes, a name should always be given, * but isn't enforced. * * @see ListOption#name() */ - public Builder<T> name(@NotNull Text name) { - Validate.notNull(name, "`name` must not be null"); - - this.name = name; - return this; - } + Builder<T> name(@NotNull Text name); /** * Sets the tooltip to be used by the list. It is displayed like a normal @@ -91,22 +72,12 @@ public interface ListOption<T> extends OptionGroup, Option<List<T>> { * * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. */ - public Builder<T> tooltip(@NotNull Text... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } + Builder<T> tooltip(@NotNull Text... tooltips); /** * Sets the value that is used when creating new entries */ - public Builder<T> initial(@NotNull T initialValue) { - Validate.notNull(initialValue, "`initialValue` cannot be empty"); - - this.initialValue = initialValue; - return this; - } + Builder<T> initial(@NotNull T initialValue); /** * Sets the controller for the option. @@ -114,12 +85,7 @@ public interface ListOption<T> extends OptionGroup, Option<List<T>> { * * @see dev.isxander.yacl.gui.controllers */ - public Builder<T> controller(@NotNull Function<ListOptionEntry<T>, Controller<T>> control) { - Validate.notNull(control, "`control` cannot be null"); - - this.controllerFunction = control; - return this; - } + Builder<T> controller(@NotNull Function<ListOptionEntry<T>, Controller<T>> control); /** * Sets the binding for the option. @@ -127,12 +93,7 @@ public interface ListOption<T> extends OptionGroup, Option<List<T>> { * * @see Binding */ - public Builder<T> binding(@NotNull Binding<List<T>> binding) { - Validate.notNull(binding, "`binding` cannot be null"); - - this.binding = binding; - return this; - } + Builder<T> binding(@NotNull Binding<List<T>> binding); /** * Sets the binding for the option. @@ -143,48 +104,28 @@ public interface ListOption<T> extends OptionGroup, Option<List<T>> { * @param setter should set the option to the supplied value * @see Binding */ - 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; - } + Builder<T> binding(@NotNull List<T> def, @NotNull Supplier<@NotNull List<T>> getter, @NotNull Consumer<@NotNull List<T>> setter); /** * Sets if the option can be configured * * @see Option#available() */ - public Builder<T> available(boolean available) { - this.available = available; - return this; - } + Builder<T> available(boolean available); /** * Adds a flag to the option. * Upon applying changes, all flags are executed. * {@link Option#flags()} */ - public Builder<T> flag(@NotNull OptionFlag... flag) { - Validate.notNull(flag, "`flag` must not be null"); - - this.flags.addAll(Arrays.asList(flag)); - return this; - } + Builder<T> flag(@NotNull OptionFlag... flag); /** * Adds a flag to the option. * Upon applying changes, all flags are executed. * {@link Option#flags()} */ - public Builder<T> flags(@NotNull Collection<OptionFlag> flags) { - Validate.notNull(flags, "`flags` must not be null"); - - this.flags.addAll(flags); - return this; - } + Builder<T> flags(@NotNull Collection<OptionFlag> flags); /** * Dictates if the group should be collapsed by default. @@ -192,26 +133,8 @@ public interface ListOption<T> extends OptionGroup, Option<List<T>> { * * @see OptionGroup#collapsed() */ - public Builder<T> collapsed(boolean collapsible) { - this.collapsed = collapsible; - return this; - } - - 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"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text 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); - } + Builder<T> collapsed(boolean collapsible); + + ListOption<T> build(); } } diff --git a/src/client/java/dev/isxander/yacl/api/Option.java b/src/client/java/dev/isxander/yacl/api/Option.java index 394723f..406931f 100644 --- a/src/client/java/dev/isxander/yacl/api/Option.java +++ b/src/client/java/dev/isxander/yacl/api/Option.java @@ -119,43 +119,16 @@ public interface Option<T> { * @param typeClass used to capture the type */ static <T> Builder<T> createBuilder(Class<T> typeClass) { - return new Builder<>(typeClass); + return new OptionImpl.BuilderImpl<>(typeClass); } - class Builder<T> { - private Text name = Text.literal("Name not specified!").formatted(Formatting.RED); - - private final List<Function<T, Text>> tooltipGetters = new ArrayList<>(); - - 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 Class<T> typeClass; - - private final List<BiConsumer<Option<T>, T>> listeners = new ArrayList<>(); - - private Builder(Class<T> typeClass) { - this.typeClass = typeClass; - } - + interface Builder<T> { /** * Sets the name to be used by the option. * * @see Option#name() */ - public Builder<T> name(@NotNull Text name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } + Builder<T> name(@NotNull Text name); /** * Sets the tooltip to be used by the option. @@ -163,13 +136,7 @@ public interface Option<T> { * * @param tooltipGetter function to get tooltip depending on value {@link Builder#build()}. */ - @SafeVarargs - public final Builder<T> tooltip(@NotNull Function<T, Text>... tooltipGetter) { - Validate.notNull(tooltipGetter, "`tooltipGetter` cannot be null"); - - this.tooltipGetters.addAll(List.of(tooltipGetter)); - return this; - } + Builder<T> tooltip(@NotNull Function<T, Text>... tooltipGetter); /** * Sets the tooltip to be used by the option. @@ -178,12 +145,7 @@ public interface Option<T> { * * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. */ - public Builder<T> tooltip(@NotNull Text... tooltips) { - Validate.notNull(tooltips, "`tooltips` cannot be empty"); - - this.tooltipGetters.addAll(Stream.of(tooltips).map(text -> (Function<T, Text>) t -> text).toList()); - return this; - } + Builder<T> tooltip(@NotNull Text... tooltips); /** * Sets the controller for the option. @@ -191,12 +153,7 @@ public interface Option<T> { * * @see dev.isxander.yacl.gui.controllers */ - public Builder<T> controller(@NotNull Function<Option<T>, Controller<T>> control) { - Validate.notNull(control, "`control` cannot be null"); - - this.controlGetter = control; - return this; - } + Builder<T> controller(@NotNull Function<Option<T>, Controller<T>> control); /** * Sets the binding for the option. @@ -204,12 +161,7 @@ public interface Option<T> { * * @see Binding */ - public Builder<T> binding(@NotNull Binding<T> binding) { - Validate.notNull(binding, "`binding` cannot be null"); - - this.binding = binding; - return this; - } + Builder<T> binding(@NotNull Binding<T> binding); /** * Sets the binding for the option. @@ -220,48 +172,28 @@ public interface Option<T> { * @param setter should set the option to the supplied value * @see Binding */ - 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; - } + Builder<T> binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter); /** * Sets if the option can be configured * * @see Option#available() */ - public Builder<T> available(boolean available) { - this.available = available; - return this; - } + Builder<T> available(boolean available); /** * Adds a flag to the option. * Upon applying changes, all flags are executed. * {@link Option#flags()} */ - public Builder<T> flag(@NotNull OptionFlag... flag) { - Validate.notNull(flag, "`flag` must not be null"); - - this.flags.addAll(Arrays.asList(flag)); - return this; - } + Builder<T> flag(@NotNull OptionFlag... flag); /** * Adds a flag to the option. * Upon applying changes, all flags are executed. * {@link Option#flags()} */ - public Builder<T> flags(@NotNull Collection<OptionFlag> flags) { - Validate.notNull(flags, "`flags` must not be null"); - - this.flags.addAll(flags); - return this; - } + Builder<T> flags(@NotNull Collection<OptionFlag> flags); /** * Instantly invokes the binder's setter when modified in the GUI. @@ -269,66 +201,22 @@ public interface Option<T> { * <p> * Does not support {@link Option#flags()}! */ - public Builder<T> instant(boolean instant) { - this.instant = instant; - return this; - } + Builder<T> instant(boolean instant); /** * Adds a listener to the option. Invoked upon changing the pending value. * * @see Option#addListener(BiConsumer) */ - public Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener) { - this.listeners.add(listener); - return this; - } + Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener); /** * Adds multiple listeners to the option. Invoked upon changing the pending value. * * @see Option#addListener(BiConsumer) */ - public Builder<T> listeners(@NotNull Collection<BiConsumer<Option<T>, T>> listeners) { - this.listeners.addAll(listeners); - return this; - } + Builder<T> listeners(@NotNull Collection<BiConsumer<Option<T>, T>> listeners); - /** - * Dictates whether the option should require a restart. - * {@link Option#requiresRestart()} - */ - @Deprecated - public Builder<T> requiresRestart(boolean requiresRestart) { - if (requiresRestart) flag(OptionFlag.GAME_RESTART); - else flags.remove(OptionFlag.GAME_RESTART); - - return this; - } - - 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"); - - Function<T, Text> concatenatedTooltipGetter = value -> { - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Function<T, Text> line : tooltipGetters) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line.apply(value)); - } - - return concatenatedTooltip; - }; - - if (instant) { - listeners.add((opt, pendingValue) -> opt.applyValue()); - } - - return new OptionImpl<>(name, concatenatedTooltipGetter, controlGetter, binding, available, ImmutableSet.copyOf(flags), typeClass, listeners); - } + Option<T> build(); } } diff --git a/src/client/java/dev/isxander/yacl/api/OptionGroup.java b/src/client/java/dev/isxander/yacl/api/OptionGroup.java index 6cc6c7f..8dd9c14 100644 --- a/src/client/java/dev/isxander/yacl/api/OptionGroup.java +++ b/src/client/java/dev/isxander/yacl/api/OptionGroup.java @@ -48,30 +48,16 @@ public interface OptionGroup { * Creates a builder to construct a {@link OptionGroup} */ static Builder createBuilder() { - return new Builder(); + return new OptionGroupImpl.BuilderImpl(); } - class Builder { - private Text name = Text.empty(); - private final List<Text> tooltipLines = new ArrayList<>(); - private final List<Option<?>> options = new ArrayList<>(); - private boolean collapsed = false; - - private Builder() { - - } - + interface Builder { /** * Sets name of the group, can be {@link Text#empty()} to just separate options, like sodium. * * @see OptionGroup#name() */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` must not be null"); - - this.name = name; - return this; - } + Builder name(@NotNull Text name); /** * Sets the tooltip to be used by the option group. @@ -80,12 +66,7 @@ public interface OptionGroup { * * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } + Builder tooltip(@NotNull Text... tooltips); /** * Adds an option to group. @@ -93,15 +74,7 @@ public interface OptionGroup { * * @see OptionGroup#options() */ - 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; - } + Builder option(@NotNull Option<?> option); /** * Adds multiple options to group. @@ -109,39 +82,15 @@ public interface OptionGroup { * * @see OptionGroup#options() */ - 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; - } + Builder options(@NotNull Collection<? extends Option<?>> options); /** * Dictates if the group should be collapsed by default * * @see OptionGroup#collapsed() */ - public Builder collapsed(boolean collapsible) { - this.collapsed = collapsible; - return this; - } - - public OptionGroup build() { - Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } + Builder collapsed(boolean collapsible); - return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), collapsed, false); - } + OptionGroup build(); } } diff --git a/src/client/java/dev/isxander/yacl/api/PlaceholderCategory.java b/src/client/java/dev/isxander/yacl/api/PlaceholderCategory.java index de7441c..7bcc821 100644 --- a/src/client/java/dev/isxander/yacl/api/PlaceholderCategory.java +++ b/src/client/java/dev/isxander/yacl/api/PlaceholderCategory.java @@ -24,31 +24,16 @@ public interface PlaceholderCategory extends ConfigCategory { BiFunction<MinecraftClient, YACLScreen, Screen> screen(); static Builder createBuilder() { - return new Builder(); + return new PlaceholderCategoryImpl.BuilderImpl(); } - class Builder { - private Text name; - - private final List<Text> tooltipLines = new ArrayList<>(); - - private BiFunction<MinecraftClient, YACLScreen, Screen> screenFunction; - - private Builder() { - - } - + interface Builder { /** * Sets name of the category * * @see ConfigCategory#name() */ - public Builder name(@NotNull Text name) { - Validate.notNull(name, "`name` cannot be null"); - - this.name = name; - return this; - } + Builder name(@NotNull Text name); /** * Sets the tooltip to be used by the category. @@ -57,38 +42,15 @@ public interface PlaceholderCategory extends ConfigCategory { * * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. */ - public Builder tooltip(@NotNull Text... tooltips) { - Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); - - tooltipLines.addAll(List.of(tooltips)); - return this; - } + Builder tooltip(@NotNull Text... tooltips); /** * Screen to open upon selecting this category * * @see PlaceholderCategory#screen() */ - public Builder screen(@NotNull BiFunction<MinecraftClient, YACLScreen, Screen> screenFunction) { - Validate.notNull(screenFunction, "`screenFunction` cannot be null"); - - this.screenFunction = screenFunction; - return this; - } - - public PlaceholderCategory build() { - Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); - - MutableText concatenatedTooltip = Text.empty(); - boolean first = true; - for (Text line : tooltipLines) { - if (!first) concatenatedTooltip.append("\n"); - first = false; - - concatenatedTooltip.append(line); - } + Builder screen(@NotNull BiFunction<MinecraftClient, YACLScreen, Screen> screenFunction); - return new PlaceholderCategoryImpl(name, screenFunction, concatenatedTooltip); - } + PlaceholderCategory build(); } } diff --git a/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java b/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java index ae6c060..f9a71d3 100644 --- a/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java +++ b/src/client/java/dev/isxander/yacl/api/YetAnotherConfigLib.java @@ -52,7 +52,7 @@ public interface YetAnotherConfigLib { * Creates a builder to construct YACL */ static Builder createBuilder() { - return new Builder(); + return new YetAnotherConfigLibImpl.BuilderImpl(); } /** @@ -63,27 +63,13 @@ public interface YetAnotherConfigLib { return builder.build(configInstance.getDefaults(), configInstance.getConfig(), createBuilder().save(configInstance::save)).build(); } - class Builder { - private Text title; - private final List<ConfigCategory> categories = new ArrayList<>(); - private Runnable saveFunction = () -> {}; - private Consumer<YACLScreen> initConsumer = screen -> {}; - - private Builder() { - - } - + interface Builder { /** * Sets title of GUI for Minecraft narration * * @see YetAnotherConfigLib#title() */ - public Builder title(@NotNull Text title) { - Validate.notNull(title, "`title` cannot be null"); - - this.title = title; - return this; - } + Builder title(@NotNull Text title); /** * Adds a new category. @@ -91,12 +77,7 @@ public interface YetAnotherConfigLib { * * @see YetAnotherConfigLib#categories() */ - public Builder category(@NotNull ConfigCategory category) { - Validate.notNull(category, "`category` cannot be null"); - - this.categories.add(category); - return this; - } + Builder category(@NotNull ConfigCategory category); /** * Adds multiple categories at once. @@ -104,44 +85,23 @@ public interface YetAnotherConfigLib { * * @see YetAnotherConfigLib#categories() */ - public Builder categories(@NotNull Collection<? extends ConfigCategory> categories) { - Validate.notNull(categories, "`categories` cannot be null"); - - this.categories.addAll(categories); - return this; - } + Builder categories(@NotNull Collection<? extends ConfigCategory> categories); /** * Used to define a save function for when user clicks the Save Changes button * * @see YetAnotherConfigLib#saveFunction() */ - public Builder save(@NotNull Runnable saveFunction) { - Validate.notNull(saveFunction, "`saveFunction` cannot be null"); - - this.saveFunction = saveFunction; - return this; - } + Builder save(@NotNull Runnable saveFunction); /** * Defines a consumer that is accepted every time the YACL screen initialises * * @see YetAnotherConfigLib#initConsumer() */ - public Builder screenInit(@NotNull Consumer<YACLScreen> initConsumer) { - Validate.notNull(initConsumer, "`initConsumer` cannot be null"); - - this.initConsumer = initConsumer; - return this; - } - - 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`"); + Builder screenInit(@NotNull Consumer<YACLScreen> initConsumer); - return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories), saveFunction, initConsumer); - } + YetAnotherConfigLib build(); } @FunctionalInterface diff --git a/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java index 8284b0e..c18597f 100644 --- a/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java +++ b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java @@ -4,6 +4,8 @@ import com.google.common.collect.ImmutableList; import dev.isxander.yacl.api.*; import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.controllers.ListEntryWidget; +import dev.isxander.yacl.impl.ListOptionEntryImpl; +import dev.isxander.yacl.impl.utils.YACLConstants; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.MultilineText; import net.minecraft.client.font.TextRenderer; @@ -65,7 +67,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr List<OptionEntry> optionEntries = new ArrayList<>(); for (Option<?> option : group.options()) { - OptionEntry entry = new OptionEntry(option, category, group, groupSeparatorEntry, option.controller().provideWidget(yaclScreen, getDefaultEntryPosition())); + OptionEntry entry = new OptionEntry(option, category, group, groupSeparatorEntry, option.controller().provideWidget(yaclScreen, getDefaultEntryDimension())); addEntry(entry); optionEntries.add(entry); } @@ -85,23 +87,31 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr // find group separator for group GroupSeparatorEntry groupSeparator = super.children().stream().filter(e -> e instanceof GroupSeparatorEntry gs && gs.group == listOption).map(GroupSeparatorEntry.class::cast).findAny().orElse(null); - if (groupSeparator == null) + if (groupSeparator == null) { + YACLConstants.LOGGER.warn("Can't find group seperator to refresh list option entries for list option " + listOption.name()); return; + } for (OptionEntry entry : groupSeparator.optionEntries) super.removeEntry(entry); - groupSeparator.optionEntries.clear(); + + // if no entries, below loop won't run where addEntryBelow() recaches viewable children + if (listOption.options().isEmpty()) { + recacheViewableChildren(); + return; + } + Entry lastEntry = groupSeparator; for (ListOptionEntry<?> listOptionEntry : listOption.options()) { - OptionEntry optionEntry = new OptionEntry(listOptionEntry, category, listOption, groupSeparator, listOptionEntry.controller().provideWidget(yaclScreen, getDefaultEntryPosition())); + OptionEntry optionEntry = new OptionEntry(listOptionEntry, category, listOption, groupSeparator, listOptionEntry.controller().provideWidget(yaclScreen, getDefaultEntryDimension())); addEntryBelow(lastEntry, optionEntry); groupSeparator.optionEntries.add(optionEntry); lastEntry = optionEntry; } } - public Dimension<Integer> getDefaultEntryPosition() { + public Dimension<Integer> getDefaultEntryDimension() { return Dimension.ofInt(getRowLeft(), 0, getRowWidth(), 20); } @@ -245,9 +255,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr this.category = category; this.group = group; this.groupSeparatorEntry = groupSeparatorEntry; - if (option instanceof ListOptionEntry<?> listOptionEntry) - this.widget = new ListEntryWidget(yaclScreen, listOptionEntry, widget); - else this.widget = widget; + this.widget = widget; this.categoryName = category.name().getString().toLowerCase(); this.groupName = group.name().getString().toLowerCase(); if (option.canResetToDefault() && this.widget.canReset()) { @@ -306,7 +314,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr @Override public int getItemHeight() { - return widget.getDimension().height() + 2; + return Math.max(widget.getDimension().height(), resetButton != null ? resetButton.getHeight() : 0) + 2; } @Override @@ -377,13 +385,16 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr } public void setExpanded(boolean expanded) { + if (this.groupExpanded == expanded) + return; + this.groupExpanded = expanded; updateExpandMinimizeText(); + recacheViewableChildren(); } protected void onExpandButtonPress() { setExpanded(!isExpanded()); - recacheViewableChildren(); } protected void updateExpandMinimizeText() { @@ -447,6 +458,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr this.addListButton = new TooltipButtonWidget(yaclScreen, resetListButton.getX() - 20, -50, 20, 20, Text.of("+"), Text.translatable("yacl.list.add_top"), btn -> { group.insertNewEntryToTop(); + setExpanded(true); }); updateExpandMinimizeText(); @@ -480,15 +492,12 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr private void minimizeIfUnavailable() { if (!listOption.available() && isExpanded()) { setExpanded(false); - recacheViewableChildren(); } } @Override protected void updateExpandMinimizeText() { super.updateExpandMinimizeText(); - if (resetListButton != null && addListButton != null) - resetListButton.visible = addListButton.visible = isExpanded(); expandMinimizeButton.active = listOption == null || listOption.available(); } diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ListEntryWidget.java b/src/client/java/dev/isxander/yacl/gui/controllers/ListEntryWidget.java index a548efb..0a5d581 100644 --- a/src/client/java/dev/isxander/yacl/gui/controllers/ListEntryWidget.java +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ListEntryWidget.java @@ -27,7 +27,7 @@ public class ListEntryWidget extends AbstractWidget implements ParentElement { private boolean dragging; public ListEntryWidget(YACLScreen screen, ListOptionEntry<?> listOptionEntry, AbstractWidget entryWidget) { - super(entryWidget.getDimension()); + super(entryWidget.getDimension().withHeight(Math.max(entryWidget.getDimension().height(), 20) - ((listOptionEntry.parentGroup().indexOf(listOptionEntry) == listOptionEntry.parentGroup().options().size() - 1) ? 0 : 2))); // -2 to remove the padding this.listOptionEntry = listOptionEntry; this.listOption = listOptionEntry.parentGroup(); this.optionNameString = listOptionEntry.name().getString().toLowerCase(); @@ -38,6 +38,7 @@ public class ListEntryWidget extends AbstractWidget implements ParentElement { removeButton = new TooltipButtonWidget(screen, dim.xLimit() - 20, dim.y(), 20, 20, Text.of("\u274c"), Text.translatable("yacl.list.remove"), btn -> { listOption.removeEntry(listOptionEntry); + updateButtonStates(); }); moveUpButton = new TooltipButtonWidget(screen, dim.x(), dim.y(), 20, 20, Text.of("\u2191"), Text.translatable("yacl.list.move_up"), btn -> { diff --git a/src/client/java/dev/isxander/yacl/impl/ButtonOptionImpl.java b/src/client/java/dev/isxander/yacl/impl/ButtonOptionImpl.java index f526d42..25260c4 100644 --- a/src/client/java/dev/isxander/yacl/impl/ButtonOptionImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/ButtonOptionImpl.java @@ -3,14 +3,21 @@ 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.text.MutableText; import net.minecraft.text.Text; +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; -public class ButtonOptionImpl implements ButtonOption { +@ApiStatus.Internal +public final class ButtonOptionImpl implements ButtonOption { private final Text name; private final Text tooltip; private final BiConsumer<YACLScreen, ButtonOption> action; @@ -134,4 +141,78 @@ public class ButtonOptionImpl implements ButtonOption { throw new UnsupportedOperationException(); } } + + @ApiStatus.Internal + public static final class BuilderImpl implements ButtonOption.Builder { + private Text name; + private final List<Text> tooltipLines = new ArrayList<>(); + private boolean available = true; + private Function<ButtonOption, Controller<BiConsumer<YACLScreen, ButtonOption>>> controlGetter; + private BiConsumer<YACLScreen, ButtonOption> action; + + @Override + public ButtonOption.Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + public ButtonOption.Builder tooltip(@NotNull Text... tooltips) { + Validate.notNull(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + @Override + public ButtonOption.Builder action(@NotNull BiConsumer<YACLScreen, ButtonOption> action) { + Validate.notNull(action, "`action` cannot be null"); + + this.action = action; + return this; + } + + @Override + @Deprecated + public ButtonOption.Builder action(@NotNull Consumer<YACLScreen> action) { + Validate.notNull(action, "`action` cannot be null"); + + this.action = (screen, button) -> action.accept(screen); + return this; + } + + @Override + public ButtonOption.Builder available(boolean available) { + this.available = available; + return this; + } + + @Override + public ButtonOption.Builder controller(@NotNull Function<ButtonOption, Controller<BiConsumer<YACLScreen, ButtonOption>>> 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`"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new ButtonOptionImpl(name, concatenatedTooltip, action, available, controlGetter); + } + } } diff --git a/src/client/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java b/src/client/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java index 971fecf..b3da814 100644 --- a/src/client/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java @@ -2,9 +2,107 @@ 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.text.MutableText; import net.minecraft.text.Text; +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 record ConfigCategoryImpl(Text name, ImmutableList<OptionGroup> groups, Text tooltip) implements ConfigCategory { + @ApiStatus.Internal + public static final class BuilderImpl implements ConfigCategory.Builder { + private Text name; + + private final List<Option<?>> rootOptions = new ArrayList<>(); + private final List<OptionGroup> groups = new ArrayList<>(); + + private final List<Text> tooltipLines = new ArrayList<>(); + + @Override + public ConfigCategory.Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + public ConfigCategory.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 ConfigCategory.Builder options(@NotNull Collection<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 ConfigCategory.Builder group(@NotNull OptionGroup group) { + Validate.notNull(group, "`group` must not be null"); + + this.groups.add(group); + return this; + } + + @Override + public ConfigCategory.Builder groups(@NotNull Collection<OptionGroup> groups) { + Validate.notEmpty(groups, "`groups` must not be empty"); + + this.groups.addAll(groups); + return this; + } + + @Override + public ConfigCategory.Builder tooltip(@NotNull Text... 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(Text.empty(), Text.empty(), ImmutableList.copyOf(rootOptions), false, true)); + combinedGroups.addAll(groups); + + Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip); + } + } } diff --git a/src/client/java/dev/isxander/yacl/impl/GenericBindingImpl.java b/src/client/java/dev/isxander/yacl/impl/GenericBindingImpl.java index 1867bb6..0d668c6 100644 --- a/src/client/java/dev/isxander/yacl/impl/GenericBindingImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/GenericBindingImpl.java @@ -5,7 +5,7 @@ import dev.isxander.yacl.api.Binding; import java.util.function.Consumer; import java.util.function.Supplier; -public class GenericBindingImpl<T> implements Binding<T> { +public final class GenericBindingImpl<T> implements Binding<T> { private final T def; private final Supplier<T> getter; private final Consumer<T> setter; diff --git a/src/client/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java b/src/client/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java index dc7aa88..98188dc 100644 --- a/src/client/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/ListOptionEntryImpl.java @@ -1,13 +1,19 @@ 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.text.Text; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.util.function.BiConsumer; import java.util.function.Function; -public class ListOptionEntryImpl<T> implements ListOptionEntry<T> { +@ApiStatus.Internal +public final class ListOptionEntryImpl<T> implements ListOptionEntry<T> { private final ListOptionImpl<T> group; private T value; @@ -15,11 +21,11 @@ public class ListOptionEntryImpl<T> implements ListOptionEntry<T> { private final Binding<T> binding; private final Controller<T> controller; - public ListOptionEntryImpl(ListOptionImpl<T> group, T initialValue, @NotNull Function<ListOptionEntry<T>, Controller<T>> controlGetter) { + 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 = controlGetter.apply(this); + this.controller = new EntryController<>(controlGetter.apply(this), this); } @Override @@ -102,6 +108,27 @@ public class ListOptionEntryImpl<T> implements ListOptionEntry<T> { } + /** + * 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 Text 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) { diff --git a/src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java b/src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java index 128d3e7..1924205 100644 --- a/src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java @@ -3,17 +3,21 @@ 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.text.MutableText; import net.minecraft.text.Text; +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.*; 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; -public class ListOptionImpl<T> implements ListOption<T> { +@ApiStatus.Internal +public final class ListOptionImpl<T> implements ListOption<T> { private final Text name; private final Text tooltip; private final Binding<List<T>> binding; @@ -109,8 +113,8 @@ public class ListOptionImpl<T> implements ListOption<T> { @Override public void removeEntry(ListOptionEntry<?> entry) { - entries.remove(entry); - onRefresh(); + if (entries.remove(entry)) + onRefresh(); } @Override @@ -123,7 +127,6 @@ public class ListOptionImpl<T> implements ListOption<T> { entries.clear(); entries.addAll(createEntries(value)); onRefresh(); - listeners.forEach(listener -> listener.accept(this, value)); } @Override @@ -205,4 +208,117 @@ public class ListOptionImpl<T> implements ListOption<T> { return new ListOptionEntryImpl<>(ListOptionImpl.this, initialValue, controllerFunction); } } + + @ApiStatus.Internal + public static final class BuilderImpl<T> implements ListOption.Builder<T> { + private Text name = Text.empty(); + private final List<Text> tooltipLines = new ArrayList<>(); + private Function<ListOptionEntry<T>, Controller<T>> controllerFunction; + private Binding<List<T>> binding = null; + private final Set<OptionFlag> flags = new HashSet<>(); + private T initialValue; + private boolean collapsed = false; + private boolean available = true; + private final Class<T> typeClass; + + public BuilderImpl(Class<T> typeClass) { + this.typeClass = typeClass; + } + + @Override + public ListOption.Builder<T> name(@NotNull Text name) { + Validate.notNull(name, "`name` must not be null"); + + this.name = name; + return this; + } + + @Override + public ListOption.Builder<T> tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + @Override + public ListOption.Builder<T> initial(@NotNull T initialValue) { + Validate.notNull(initialValue, "`initialValue` cannot be empty"); + + this.initialValue = initialValue; + return this; + } + + @Override + public ListOption.Builder<T> controller(@NotNull Function<ListOptionEntry<T>, Controller<T>> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controllerFunction = control; + return this; + } + + @Override + public ListOption.Builder<T> binding(@NotNull Binding<List<T>> binding) { + Validate.notNull(binding, "`binding` cannot be null"); + + this.binding = binding; + return this; + } + + @Override + public ListOption.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 ListOption.Builder<T> available(boolean available) { + this.available = available; + return this; + } + + @Override + public ListOption.Builder<T> flag(@NotNull OptionFlag... flag) { + Validate.notNull(flag, "`flag` must not be null"); + + this.flags.addAll(Arrays.asList(flag)); + return this; + } + + @Override + public ListOption.Builder<T> flags(@NotNull Collection<OptionFlag> flags) { + Validate.notNull(flags, "`flags` must not be null"); + + this.flags.addAll(flags); + return this; + } + + @Override + public ListOption.Builder<T> collapsed(boolean collapsible) { + this.collapsed = collapsible; + 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"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text 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); + } + } } diff --git a/src/client/java/dev/isxander/yacl/impl/OptionGroupImpl.java b/src/client/java/dev/isxander/yacl/impl/OptionGroupImpl.java index 02ef04c..89a2adf 100644 --- a/src/client/java/dev/isxander/yacl/impl/OptionGroupImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/OptionGroupImpl.java @@ -1,10 +1,86 @@ 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.text.MutableText; import net.minecraft.text.Text; +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 record OptionGroupImpl(@NotNull Text name, @NotNull Text tooltip, ImmutableList<? extends Option<?>> options, boolean collapsed, boolean isRoot) implements OptionGroup { + @ApiStatus.Internal + public static final class BuilderImpl implements OptionGroup.Builder { + private Text name = Text.empty(); + private final List<Text> tooltipLines = new ArrayList<>(); + private final List<Option<?>> options = new ArrayList<>(); + private boolean collapsed = false; + + @Override + public OptionGroup.Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` must not be null"); + + this.name = name; + return this; + } + + @Override + public OptionGroup.Builder tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + @Override + public OptionGroup.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 OptionGroup.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 OptionGroup.Builder collapsed(boolean collapsible) { + this.collapsed = collapsible; + return this; + } + + @Override + public OptionGroup build() { + Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), collapsed, false); + } + } } diff --git a/src/client/java/dev/isxander/yacl/impl/OptionImpl.java b/src/client/java/dev/isxander/yacl/impl/OptionImpl.java index 0cc156f..d2815e1 100644 --- a/src/client/java/dev/isxander/yacl/impl/OptionImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/OptionImpl.java @@ -5,17 +5,23 @@ 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.text.MutableText; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +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.Collection; -import java.util.List; +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.Stream; -public class OptionImpl<T> implements Option<T> { +@ApiStatus.Internal +public final class OptionImpl<T> implements Option<T> { private final Text name; private Text tooltip; private final Controller<T> controller; @@ -136,4 +142,146 @@ public class OptionImpl<T> implements Option<T> { public void addListener(BiConsumer<Option<T>, T> changedListener) { this.listeners.add(changedListener); } + + @ApiStatus.Internal + public static class BuilderImpl<T> implements Option.Builder<T> { + private Text name = Text.literal("Name not specified!").formatted(Formatting.RED); + + private final List<Function<T, Text>> tooltipGetters = new ArrayList<>(); + + 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 Class<T> typeClass; + + private final List<BiConsumer<Option<T>, T>> listeners = new ArrayList<>(); + + public BuilderImpl(Class<T> typeClass) { + this.typeClass = typeClass; + } + + @Override + public Option.Builder<T> name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + @SafeVarargs + public final Option.Builder<T> tooltip(@NotNull Function<T, Text>... tooltipGetter) { + Validate.notNull(tooltipGetter, "`tooltipGetter` cannot be null"); + + this.tooltipGetters.addAll(List.of(tooltipGetter)); + return this; + } + + @Override + public Option.Builder<T> tooltip(@NotNull Text... tooltips) { + Validate.notNull(tooltips, "`tooltips` cannot be empty"); + + this.tooltipGetters.addAll(Stream.of(tooltips).map(text -> (Function<T, Text>) t -> text).toList()); + return this; + } + + @Override + public Option.Builder<T> controller(@NotNull Function<Option<T>, Controller<T>> control) { + Validate.notNull(control, "`control` cannot be null"); + + this.controlGetter = control; + return this; + } + + @Override + public Option.Builder<T> binding(@NotNull Binding<T> binding) { + Validate.notNull(binding, "`binding` cannot be null"); + + this.binding = binding; + return this; + } + + @Override + public Option.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 Option.Builder<T> available(boolean available) { + this.available = available; + return this; + } + + @Override + public Option.Builder<T> flag(@NotNull OptionFlag... flag) { + Validate.notNull(flag, "`flag` must not be null"); + + this.flags.addAll(Arrays.asList(flag)); + return this; + } + + @Override + public Option.Builder<T> flags(@NotNull Collection<OptionFlag> flags) { + Validate.notNull(flags, "`flags` must not be null"); + + this.flags.addAll(flags); + return this; + } + + @Override + public Option.Builder<T> instant(boolean instant) { + this.instant = instant; + return this; + } + + @Override + public Option.Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener) { + this.listeners.add(listener); + return this; + } + + @Override + public Option.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"); + + Function<T, Text> concatenatedTooltipGetter = value -> { + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Function<T, Text> line : tooltipGetters) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line.apply(value)); + } + + 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/src/client/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java b/src/client/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java index a5180ad..28e5886 100644 --- a/src/client/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java @@ -1,19 +1,75 @@ package dev.isxander.yacl.impl; import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.ConfigCategory; import dev.isxander.yacl.api.OptionGroup; import dev.isxander.yacl.api.PlaceholderCategory; import dev.isxander.yacl.gui.YACLScreen; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; +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 record PlaceholderCategoryImpl(Text name, BiFunction<MinecraftClient, YACLScreen, Screen> screen, Text tooltip) implements PlaceholderCategory { @Override public @NotNull ImmutableList<OptionGroup> groups() { return ImmutableList.of(); } + + @ApiStatus.Internal + public static final class BuilderImpl implements PlaceholderCategory.Builder { + private Text name; + + private final List<Text> tooltipLines = new ArrayList<>(); + + private BiFunction<MinecraftClient, YACLScreen, Screen> screenFunction; + + @Override + public PlaceholderCategory.Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + @Override + public PlaceholderCategory.Builder tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + @Override + public PlaceholderCategory.Builder screen(@NotNull BiFunction<MinecraftClient, 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`"); + + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new PlaceholderCategoryImpl(name, screenFunction, concatenatedTooltip); + } + } } diff --git a/src/client/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java b/src/client/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java index 380929c..576c1bf 100644 --- a/src/client/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java +++ b/src/client/java/dev/isxander/yacl/impl/YetAnotherConfigLibImpl.java @@ -2,15 +2,23 @@ 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.screen.Screen; import net.minecraft.text.Text; +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.Objects; import java.util.function.Consumer; +@ApiStatus.Internal public final class YetAnotherConfigLibImpl implements YetAnotherConfigLib { private final Text title; private final ImmutableList<ConfigCategory> categories; @@ -56,29 +64,60 @@ public final class YetAnotherConfigLibImpl implements YetAnotherConfigLib { return initConsumer; } - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (YetAnotherConfigLibImpl) obj; - return Objects.equals(this.title, that.title) && - Objects.equals(this.categories, that.categories) && - Objects.equals(this.saveFunction, that.saveFunction) && - Objects.equals(this.initConsumer, that.initConsumer); - } + @ApiStatus.Internal + public static final class BuilderImpl implements YetAnotherConfigLib.Builder { + private Text title; + private final List<ConfigCategory> categories = new ArrayList<>(); + private Runnable saveFunction = () -> {}; + private Consumer<YACLScreen> initConsumer = screen -> {}; - @Override - public int hashCode() { - return Objects.hash(title, categories, saveFunction, initConsumer); - } + @Override + public YetAnotherConfigLib.Builder title(@NotNull Text title) { + Validate.notNull(title, "`title` cannot be null"); - @Override - public String toString() { - return "YetAnotherConfigLibImpl[" + - "title=" + title + ", " + - "categories=" + categories + ", " + - "saveFunction=" + saveFunction + ", " + - "initConsumer=" + initConsumer + ']'; - } + this.title = title; + return this; + } + + @Override + public YetAnotherConfigLib.Builder category(@NotNull ConfigCategory category) { + Validate.notNull(category, "`category` cannot be null"); + + this.categories.add(category); + return this; + } + @Override + public YetAnotherConfigLib.Builder categories(@NotNull Collection<? extends ConfigCategory> categories) { + Validate.notNull(categories, "`categories` cannot be null"); + + this.categories.addAll(categories); + return this; + } + + @Override + public YetAnotherConfigLib.Builder save(@NotNull Runnable saveFunction) { + Validate.notNull(saveFunction, "`saveFunction` cannot be null"); + + this.saveFunction = saveFunction; + return this; + } + + @Override + public YetAnotherConfigLib.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/client/java/dev/isxander/yacl/mixin/client/SimpleOptionAccessor.java b/src/client/java/dev/isxander/yacl/mixin/client/SimpleOptionAccessor.java index a48c808..4333f37 100644 --- a/src/client/java/dev/isxander/yacl/mixin/client/SimpleOptionAccessor.java +++ b/src/client/java/dev/isxander/yacl/mixin/client/SimpleOptionAccessor.java @@ -1,9 +1,11 @@ package dev.isxander.yacl.mixin.client; import net.minecraft.client.option.SimpleOption; +import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +@ApiStatus.Internal @Mixin(SimpleOption.class) public interface SimpleOptionAccessor<T> { @Accessor |