diff options
author | isXander <isxander@users.noreply.github.com> | 2022-09-18 19:14:58 +0100 |
---|---|---|
committer | isXander <isxander@users.noreply.github.com> | 2022-09-18 19:15:09 +0100 |
commit | 33e98c7edc1404e099f9c9bcc586fd5c55cb8bdd (patch) | |
tree | a964082d62c3b13f5628c036fe49f9ea19d32315 | |
parent | 564a028eed25cfce0e0486f1a1a21affb499a311 (diff) | |
download | YetAnotherConfigLib-33e98c7edc1404e099f9c9bcc586fd5c55cb8bdd.tar.gz YetAnotherConfigLib-33e98c7edc1404e099f9c9bcc586fd5c55cb8bdd.tar.bz2 YetAnotherConfigLib-33e98c7edc1404e099f9c9bcc586fd5c55cb8bdd.zip |
1.3.0
option tooltips now consume the pending value
PlaceholderCategory: a category that when selected, just opens a screen
11 files changed, 198 insertions, 52 deletions
diff --git a/build.gradle.kts b/build.gradle.kts index 7e46d96..74f830a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ plugins { val ciRun = System.getenv().containsKey("GITHUB_ACTIONS") group = "dev.isxander" -version = "1.2.1" +version = "1.3.0" if (ciRun) version = "$version-SNAPSHOT" diff --git a/changelogs/1.3.0.md b/changelogs/1.3.0.md new file mode 100644 index 0000000..f088eb5 --- /dev/null +++ b/changelogs/1.3.0.md @@ -0,0 +1,2 @@ +- Option tooltips now have access to the pending value to dynamically change +- `PlaceholderCategory`: A category that when selected, just opens a screen diff --git a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java b/src/main/java/dev/isxander/yacl/api/ConfigCategory.java index 1b2a2bc..27c3e95 100644 --- a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java +++ b/src/main/java/dev/isxander/yacl/api/ConfigCategory.java @@ -90,7 +90,7 @@ public interface ConfigCategory { * @see OptionGroup#isRoot() */ public Builder options(@NotNull Collection<Option<?>> options) { - Validate.notEmpty(options, "`options` must not be empty"); + Validate.notNull(options, "`options` must not be null"); this.rootOptions.addAll(options); return this; diff --git a/src/main/java/dev/isxander/yacl/api/Option.java b/src/main/java/dev/isxander/yacl/api/Option.java index ff72c4c..1c3b006 100644 --- a/src/main/java/dev/isxander/yacl/api/Option.java +++ b/src/main/java/dev/isxander/yacl/api/Option.java @@ -9,9 +9,11 @@ import org.apache.commons.lang3.Validate; 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.Stream; public interface Option<T> { /** @@ -95,6 +97,8 @@ public interface Option<T> { */ void requestSetDefault(); + void addListener(BiConsumer<Option<T>, T> changedListener); + /** * Creates a builder to construct an {@link Option} * @@ -108,7 +112,7 @@ public interface Option<T> { class Builder<T> { private Text name = Text.literal("Name not specified!").formatted(Formatting.RED); - private final List<Text> tooltipLines = new ArrayList<>(); + private final List<Function<T, Text>> tooltipGetters = new ArrayList<>(); private Function<Option<T>, Controller<T>> controlGetter; @@ -138,6 +142,20 @@ public interface Option<T> { /** * Sets the tooltip to be used by the option. + * No need to wrap the text yourself, the gui does this itself. + * + * @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; + } + + /** + * Sets the tooltip to be used by the option. * Can be invoked twice to append more lines. * No need to wrap the text yourself, the gui does this itself. * @@ -146,7 +164,7 @@ public interface Option<T> { public Builder<T> tooltip(@NotNull Text... tooltips) { Validate.notNull(tooltips, "`tooltips` cannot be empty"); - tooltipLines.addAll(List.of(tooltips)); + this.tooltipGetters.addAll(Stream.of(tooltips).map(text -> (Function<T, Text>) t -> text).toList()); return this; } @@ -244,16 +262,20 @@ public interface Option<T> { Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); Validate.notNull(binding, "`binding` 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; + 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)); + } - concatenatedTooltip.append(line); - } + return concatenatedTooltip; + }; - return new OptionImpl<>(name, concatenatedTooltip, controlGetter, binding, available, ImmutableSet.copyOf(flags), typeClass); + return new OptionImpl<>(name, concatenatedTooltipGetter, controlGetter, binding, available, ImmutableSet.copyOf(flags), typeClass); } } } diff --git a/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java b/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java new file mode 100644 index 0000000..de7441c --- /dev/null +++ b/src/main/java/dev/isxander/yacl/api/PlaceholderCategory.java @@ -0,0 +1,94 @@ +package dev.isxander.yacl.api; + +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.impl.PlaceholderCategoryImpl; +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.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +/** + * A placeholder category that actually just opens another screen, + * instead of displaying options + */ +public interface PlaceholderCategory extends ConfigCategory { + /** + * Function to create a screen to open upon changing to this category + */ + BiFunction<MinecraftClient, YACLScreen, Screen> screen(); + + static Builder createBuilder() { + return new Builder(); + } + + class Builder { + private Text name; + + private final List<Text> tooltipLines = new ArrayList<>(); + + private BiFunction<MinecraftClient, YACLScreen, Screen> screenFunction; + + private 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; + } + + /** + * Sets the tooltip to be used by the category. + * Can be invoked twice to append more lines. + * No need to wrap the text yourself, the gui does this itself. + * + * @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; + } + + /** + * 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); + } + + return new PlaceholderCategoryImpl(name, screenFunction, concatenatedTooltip); + } + } +} diff --git a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java index 26f4ad5..80ffb0e 100644 --- a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java +++ b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java @@ -1,10 +1,7 @@ package dev.isxander.yacl.gui; import com.mojang.blaze3d.systems.RenderSystem; -import dev.isxander.yacl.api.ConfigCategory; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.OptionFlag; -import dev.isxander.yacl.api.YetAnotherConfigLib; +import dev.isxander.yacl.api.*; import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.api.utils.OptionUtils; import dev.isxander.yacl.impl.YACLConstants; @@ -32,13 +29,11 @@ public class YACLScreen extends Screen { private final Screen parent; public OptionListWidget optionList; -// public final List<CategoryWidget> categoryButtons; public CategoryListWidget categoryList; public TooltipButtonWidget finishedSaveButton, cancelResetButton, undoButton; public SearchFieldWidget searchFieldWidget; - public Text saveButtonMessage; - public Text saveButtonTooltipMessage; + public Text saveButtonMessage, saveButtonTooltipMessage; private int saveButtonMessageTime; @@ -46,34 +41,15 @@ public class YACLScreen extends Screen { super(config.title()); this.config = config; this.parent = parent; -// this.categoryButtons = new ArrayList<>(); this.currentCategoryIdx = 0; } @Override protected void init() { -// categoryButtons.clear(); int columnWidth = width / 3; int padding = columnWidth / 20; columnWidth = Math.min(columnWidth, 400); int paddedWidth = columnWidth - padding * 2; -// Dimension<Integer> categoryDim = Dimension.ofInt(width / 3 / 2, padding, paddedWidth, 20); -// int idx = 0; -// for (ConfigCategory category : config.categories()) { -// CategoryWidget categoryWidget = new CategoryWidget( -// this, -// category, -// idx, -// categoryDim.x() - categoryDim.width() / 2, categoryDim.y(), -// categoryDim.width(), categoryDim.height() -// ); -// -// categoryButtons.add(categoryWidget); -// addDrawableChild(categoryWidget); -// -// idx++; -// categoryDim.move(0, 21); -// } Dimension<Integer> actionDim = Dimension.ofInt(width / 3 / 2, height - padding - 20, paddedWidth, 20); finishedSaveButton = new TooltipButtonWidget(this, actionDim.x() - actionDim.width() / 2, actionDim.y(), actionDim.width(), actionDim.height(), Text.empty(), Text.empty(), (btn) -> { @@ -167,8 +143,12 @@ public class YACLScreen extends Screen { } public void changeCategory(int idx) { - currentCategoryIdx = idx; - optionList.refreshOptions(); + if (currentCategoryIdx != -1 && config.categories().get(idx) instanceof PlaceholderCategory placeholderCategory) { + client.setScreen(placeholderCategory.screen().apply(client, this)); + } else { + currentCategoryIdx = idx; + optionList.refreshOptions(); + } } private void updateActionAvailability() { diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java b/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java index 1c986e4..cd70872 100644 --- a/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java +++ b/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java @@ -16,7 +16,7 @@ import java.util.List; public abstract class ControllerWidget<T extends Controller<?>> extends AbstractWidget { protected final T control; - protected final MultilineText wrappedTooltip; + protected MultilineText wrappedTooltip; protected final YACLScreen screen; protected boolean focused = false; @@ -29,7 +29,8 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract super(dim); this.control = control; this.screen = screen; - this.wrappedTooltip = MultilineText.create(textRenderer, control.option().tooltip(), screen.width / 2); + control.option().addListener((opt, pending) -> updateTooltip()); + updateTooltip(); } @Override @@ -71,7 +72,7 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract @Override public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (hoveredTicks > YACLConstants.HOVER_TICKS) { + if (hoveredTicks >= YACLConstants.HOVER_TICKS) { YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, mouseX, mouseY, screen.width, screen.height); } } @@ -94,6 +95,10 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract return this.dim.isPointInside((int) mouseX, (int) mouseY); } + private void updateTooltip() { + this.wrappedTooltip = MultilineText.create(textRenderer, control.option().tooltip(), screen.width / 2); + } + protected int getControlWidth() { return isHovered() ? getHoveredControlWidth() : getUnhoveredControlWidth(); } diff --git a/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java b/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java index a600bc3..51c94b0 100644 --- a/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java +++ b/src/main/java/dev/isxander/yacl/impl/ButtonOptionImpl.java @@ -1,16 +1,14 @@ package dev.isxander.yacl.impl; import com.google.common.collect.ImmutableSet; -import dev.isxander.yacl.api.Binding; -import dev.isxander.yacl.api.ButtonOption; -import dev.isxander.yacl.api.Controller; -import dev.isxander.yacl.api.OptionFlag; +import dev.isxander.yacl.api.*; import dev.isxander.yacl.gui.YACLScreen; import net.minecraft.text.Text; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -113,6 +111,11 @@ public class ButtonOptionImpl implements ButtonOption { } + @Override + public void addListener(BiConsumer<Option<Consumer<YACLScreen>>, Consumer<YACLScreen>> changedListener) { + + } + private static class EmptyBinderImpl implements Binding<Consumer<YACLScreen>> { @Override public void setValue(Consumer<YACLScreen> value) { diff --git a/src/main/java/dev/isxander/yacl/impl/OptionImpl.java b/src/main/java/dev/isxander/yacl/impl/OptionImpl.java index 5a31a70..1cf2a79 100644 --- a/src/main/java/dev/isxander/yacl/impl/OptionImpl.java +++ b/src/main/java/dev/isxander/yacl/impl/OptionImpl.java @@ -10,12 +10,16 @@ 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.Function; @ApiStatus.Internal public class OptionImpl<T> implements Option<T> { private final Text name; - private final Text tooltip; + private Text tooltip; + private final Function<T, Text> tooltipGetter; private final Controller<T> controller; private final Binding<T> binding; private final boolean available; @@ -26,9 +30,11 @@ public class OptionImpl<T> implements Option<T> { private T pendingValue; + private final List<BiConsumer<Option<T>, T>> listeners; + public OptionImpl( @NotNull Text name, - @Nullable Text tooltip, + @Nullable Function<T, Text> tooltipGetter, @NotNull Function<Option<T>, Controller<T>> controlGetter, @NotNull Binding<T> binding, boolean available, @@ -36,13 +42,16 @@ public class OptionImpl<T> implements Option<T> { @NotNull Class<T> typeClass ) { this.name = name; - this.tooltip = tooltip; + this.tooltipGetter = tooltipGetter; this.controller = controlGetter.apply(this); this.binding = binding; this.available = available; this.flags = flags; this.typeClass = typeClass; - this.pendingValue = binding().getValue(); + this.listeners = new ArrayList<>(); + + addListener((opt, pending) -> tooltip = tooltipGetter.apply(pending)); + requestSet(binding().getValue()); } @Override @@ -98,6 +107,7 @@ public class OptionImpl<T> implements Option<T> { @Override public void requestSet(T value) { pendingValue = value; + listeners.forEach(listener -> listener.accept(this, pendingValue)); } @Override @@ -118,4 +128,9 @@ public class OptionImpl<T> implements Option<T> { public void requestSetDefault() { pendingValue = binding().defaultValue(); } + + @Override + public void addListener(BiConsumer<Option<T>, T> changedListener) { + this.listeners.add(changedListener); + } } diff --git a/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java b/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java new file mode 100644 index 0000000..a5180ad --- /dev/null +++ b/src/main/java/dev/isxander/yacl/impl/PlaceholderCategoryImpl.java @@ -0,0 +1,19 @@ +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.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiFunction; + +public record PlaceholderCategoryImpl(Text name, BiFunction<MinecraftClient, YACLScreen, Screen> screen, Text tooltip) implements PlaceholderCategory { + @Override + public @NotNull ImmutableList<OptionGroup> groups() { + return ImmutableList.of(); + } +} diff --git a/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java b/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java index df81768..944246d 100644 --- a/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java +++ b/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java @@ -3,6 +3,7 @@ package dev.isxander.yacl.test; import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; import dev.isxander.yacl.api.*; +import dev.isxander.yacl.gui.RequireRestartScreen; import dev.isxander.yacl.gui.controllers.*; import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController; import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; @@ -10,6 +11,7 @@ import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; import dev.isxander.yacl.gui.controllers.slider.LongSliderController; import dev.isxander.yacl.gui.controllers.string.StringController; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.MessageScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.option.GraphicsMode; import net.minecraft.client.toast.SystemToast; @@ -60,7 +62,7 @@ public class ModMenuIntegration implements ModMenuApi { .collapsed(true) .option(Option.createBuilder(boolean.class) .name(Text.of("Boolean Toggle")) - .tooltip(Text.of("A simple toggle button.")) + .tooltip(value -> Text.of("A simple toggle button that contains the value '" + value + "'")) .binding( false, () -> TestSettings.booleanToggle, @@ -190,6 +192,10 @@ public class ModMenuIntegration implements ModMenuApi { .build()) .build()) .build()) + .category(PlaceholderCategory.createBuilder() + .name(Text.of("Placeholder Category")) + .screen((client, yaclScreen) -> new RequireRestartScreen(yaclScreen)) + .build()) .category(ConfigCategory.createBuilder() .name(Text.of("Group Test")) .option(Option.createBuilder(boolean.class) |