diff options
| author | xander <xander@isxander.dev> | 2022-09-01 08:57:59 +0100 |
|---|---|---|
| committer | xander <xander@isxander.dev> | 2022-09-01 08:57:59 +0100 |
| commit | 6f8ef7daaafd71090b2c334c10eadc8dedc738d9 (patch) | |
| tree | d4054a65d99070c944132be83d25e109750dc5f9 /src/main/java/dev | |
| parent | 9d0a5e937f97c1c17d034393e01636d5241f376a (diff) | |
| download | YetAnotherConfigLib-6f8ef7daaafd71090b2c334c10eadc8dedc738d9.tar.gz YetAnotherConfigLib-6f8ef7daaafd71090b2c334c10eadc8dedc738d9.tar.bz2 YetAnotherConfigLib-6f8ef7daaafd71090b2c334c10eadc8dedc738d9.zip | |
GUI Implementation
Added groups
Added button "option"
Added test mod
Diffstat (limited to 'src/main/java/dev')
29 files changed, 1520 insertions, 39 deletions
diff --git a/src/main/java/dev/isxander/yacl/api/Binding.java b/src/main/java/dev/isxander/yacl/api/Binding.java index 67ff822..74120a0 100644 --- a/src/main/java/dev/isxander/yacl/api/Binding.java +++ b/src/main/java/dev/isxander/yacl/api/Binding.java @@ -10,7 +10,7 @@ public interface Binding<T> { T getValue(); - void resetValue(); + T defaultValue(); static <T> Binding<T> of(T def, Supplier<T> getter, Consumer<T> setter) { return new GenericBindingImpl<>(def, getter, setter); diff --git a/src/main/java/dev/isxander/yacl/api/ButtonOption.java b/src/main/java/dev/isxander/yacl/api/ButtonOption.java new file mode 100644 index 0000000..08436b3 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/api/ButtonOption.java @@ -0,0 +1,75 @@ +package dev.isxander.yacl.api; + +import dev.isxander.yacl.impl.ButtonOptionImpl; +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.Function; + +public interface ButtonOption extends Option<Runnable> { + Runnable action(); + + static Builder createBuilder() { + return new Builder(); + } + + class Builder { + private Text name; + private final List<Text> tooltipLines = new ArrayList<>(); + private Function<ButtonOption, Control<Runnable>> controlGetter; + private Runnable action; + + private Builder() { + + } + + public Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` cannot be null"); + + this.name = name; + return this; + } + + public Builder tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + public Builder action(@NotNull Runnable action) { + Validate.notNull(action, "`action` cannot be null"); + + this.action = action; + return this; + } + + public Builder controller(@NotNull Function<ButtonOption, Control<Runnable>> 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); + } + + return new ButtonOptionImpl(name, concatenatedTooltip, action, controlGetter); + } + } +} diff --git a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java b/src/main/java/dev/isxander/yacl/api/ConfigCategory.java index ee2fbc7..13552d6 100644 --- a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java +++ b/src/main/java/dev/isxander/yacl/api/ConfigCategory.java @@ -1,18 +1,22 @@ package dev.isxander.yacl.api; import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.gui.YACLScreen; import dev.isxander.yacl.impl.ConfigCategoryImpl; +import dev.isxander.yacl.impl.OptionGroupImpl; +import net.minecraft.client.gui.widget.ClickableWidget; 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.Function; public interface ConfigCategory { @NotNull Text name(); - @NotNull ImmutableList<Option<?>> options(); + @NotNull ImmutableList<OptionGroup> groups(); static Builder createBuilder() { return new Builder(); @@ -20,31 +24,44 @@ public interface ConfigCategory { class Builder { private Text name; - private final List<Option<?>> options = new ArrayList<>(); + private final List<Option<?>> rootOptions = new ArrayList<>(); + + private final List<OptionGroup> groups = new ArrayList<>(); private Builder() { } - public Builder setName(@NotNull Text name) { + public Builder name(@NotNull Text name) { Validate.notNull(name, "`name` cannot be null"); this.name = name; return this; } - public Builder addOption(@NotNull Option<?> option) { + public Builder option(@NotNull Option<?> option) { Validate.notNull(option, "`option` must not be null"); - this.options.add(option); + this.rootOptions.add(option); + return this; + } + + public Builder group(@NotNull OptionGroup group) { + Validate.notNull(group, "`group` must not be null"); + + this.groups.add(group); return this; } public ConfigCategory build() { Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); - Validate.notEmpty(options, "`at least one option must be added to build `ConfigCategory`"); + Validate.notEmpty(rootOptions, "`at least one option must be added to build `ConfigCategory`"); + + List<OptionGroup> combinedGroups = new ArrayList<>(); + combinedGroups.add(new OptionGroupImpl(Text.empty(), ImmutableList.copyOf(rootOptions), true)); + combinedGroups.addAll(groups); - return new ConfigCategoryImpl(name, ImmutableList.copyOf(options)); + return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups)); } } } diff --git a/src/main/java/dev/isxander/yacl/api/Control.java b/src/main/java/dev/isxander/yacl/api/Control.java index 0733c4f..242b2c8 100644 --- a/src/main/java/dev/isxander/yacl/api/Control.java +++ b/src/main/java/dev/isxander/yacl/api/Control.java @@ -1,10 +1,14 @@ package dev.isxander.yacl.api; import dev.isxander.yacl.api.utils.Dimension; -import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.controllers.ControlWidget; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; public interface Control<T> { Option<T> option(); - AbstractWidget provideWidget(Dimension<Integer> widgetDimension); + Text formatValue(); + + ControlWidget<?> provideWidget(Screen screen, Dimension<Integer> widgetDimension); } diff --git a/src/main/java/dev/isxander/yacl/api/NameableEnum.java b/src/main/java/dev/isxander/yacl/api/NameableEnum.java new file mode 100644 index 0000000..12a58c3 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/api/NameableEnum.java @@ -0,0 +1,7 @@ +package dev.isxander.yacl.api; + +import net.minecraft.text.Text; + +public interface NameableEnum { + Text getDisplayName(); +} diff --git a/src/main/java/dev/isxander/yacl/api/Option.java b/src/main/java/dev/isxander/yacl/api/Option.java index 1c7a8a1..5a98d50 100644 --- a/src/main/java/dev/isxander/yacl/api/Option.java +++ b/src/main/java/dev/isxander/yacl/api/Option.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; public interface Option<T> { @@ -23,11 +24,17 @@ public interface Option<T> { boolean changed(); + T pendingValue(); + void requestSet(T value); void applyValue(); - static <T> Builder<T> createBuilder() { + void forgetPendingValue(); + + void requestSetDefault(); + + static <T> Builder<T> createBuilder(Class<T> clazz) { return new Builder<>(); } @@ -36,7 +43,7 @@ public interface Option<T> { private final List<Text> tooltipLines = new ArrayList<>(); - private Control<T> control; + private Function<Option<T>, Control<T>> controlGetter; private Binding<T> binding; @@ -58,10 +65,10 @@ public interface Option<T> { return this; } - public Builder<T> controller(@NotNull Control<T> control) { + public Builder<T> controller(@NotNull Function<Option<T>, Control<T>> control) { Validate.notNull(control, "`control` cannot be null"); - this.control = control; + this.controlGetter = control; return this; } @@ -73,7 +80,7 @@ public interface Option<T> { } public Builder<T> binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) { - Validate.notNull(def, "`default` must not be null"); + Validate.notNull(def, "`def` must not be null"); Validate.notNull(getter, "`getter` must not be null"); Validate.notNull(setter, "`setter` must not be null"); @@ -83,7 +90,7 @@ public interface Option<T> { public Option<T> build() { Validate.notNull(name, "`name` must not be null when building `Option`"); - Validate.notNull(control, "`control` must not be null when building `Option`"); + 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(); @@ -95,7 +102,7 @@ public interface Option<T> { concatenatedTooltip.append(line); } - return new OptionImpl<>(name, concatenatedTooltip, control, binding); + return new OptionImpl<>(name, concatenatedTooltip, controlGetter, binding); } } } diff --git a/src/main/java/dev/isxander/yacl/api/OptionGroup.java b/src/main/java/dev/isxander/yacl/api/OptionGroup.java new file mode 100644 index 0000000..bedbc82 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/api/OptionGroup.java @@ -0,0 +1,52 @@ +package dev.isxander.yacl.api; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.impl.OptionGroupImpl; +import net.minecraft.text.Text; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public interface OptionGroup { + Text name(); + + @NotNull ImmutableList<Option<?>> options(); + + boolean isRoot(); + + static Builder createBuilder() { + return new Builder(); + } + + class Builder { + private Text name = Text.empty(); + private final List<Option<?>> options = new ArrayList<>(); + + private Builder() { + + } + + public Builder name(@NotNull Text name) { + Validate.notNull(name, "`name` must not be null"); + + this.name = name; + return this; + } + + public Builder option(@NotNull Option<?> option) { + Validate.notNull(option, "`option` must not be null"); + + this.options.add(option); + return this; + } + + public OptionGroup build() { + Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`"); + + return new OptionGroupImpl(name, ImmutableList.copyOf(options), false); + } + } +} diff --git a/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java b/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java index d7f8416..a598e27 100644 --- a/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java +++ b/src/main/java/dev/isxander/yacl/api/YetAnotherConfigLib.java @@ -1,14 +1,17 @@ package dev.isxander.yacl.api; import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.gui.YACLScreen; import dev.isxander.yacl.impl.YetAnotherConfigLibImpl; import net.minecraft.client.gui.screen.Screen; import net.minecraft.text.Text; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; public interface YetAnotherConfigLib { @@ -16,7 +19,11 @@ public interface YetAnotherConfigLib { ImmutableList<ConfigCategory> categories(); - Screen generateScreen(); + Runnable saveFunction(); + + Consumer<YACLScreen> initConsumer(); + + Screen generateScreen(@Nullable Screen parent); static Builder createBuilder(Text title) { return new Builder(title); @@ -25,31 +32,47 @@ public interface YetAnotherConfigLib { class Builder { private Text title; private final List<ConfigCategory> categories = new ArrayList<>(); + private Runnable saveFunction = () -> {}; + private Consumer<YACLScreen> initConsumer = screen -> {}; private Builder(@NotNull Text title) { Validate.notNull(title, "`title` cannot be null"); this.title = title; } - public Builder setTitle(@NotNull Text title) { + public Builder title(@NotNull Text title) { Validate.notNull(title, "`title` cannot be null"); this.title = title; return this; } - public Builder addCategory(@NotNull ConfigCategory category) { + public Builder category(@NotNull ConfigCategory category) { Validate.notNull(category, "`category` cannot be null"); this.categories.add(category); return this; } + public Builder save(@NotNull Runnable saveFunction) { + Validate.notNull(saveFunction, "`saveFunction` cannot be null"); + + this.saveFunction = saveFunction; + return this; + } + + 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`"); - return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories)); + return new YetAnotherConfigLibImpl(title, ImmutableList.copyOf(categories), saveFunction, initConsumer); } } } diff --git a/src/main/java/dev/isxander/yacl/api/utils/Dimension.java b/src/main/java/dev/isxander/yacl/api/utils/Dimension.java index cf7127a..69958b1 100644 --- a/src/main/java/dev/isxander/yacl/api/utils/Dimension.java +++ b/src/main/java/dev/isxander/yacl/api/utils/Dimension.java @@ -12,8 +12,29 @@ public interface Dimension<T extends Number> { T xLimit(); T yLimit(); + T centerX(); + T centerY(); + boolean isPointInside(T x, T y); + Dimension<T> clone(); + + Dimension<T> setX(T x); + Dimension<T> setY(T y); + Dimension<T> setWidth(T width); + Dimension<T> setHeight(T height); + + Dimension<T> withX(T x); + Dimension<T> withY(T y); + Dimension<T> withWidth(T width); + Dimension<T> withHeight(T height); + + Dimension<T> move(T x, T y); + Dimension<T> expand(T width, T height); + + Dimension<T> moved(T x, T y); + Dimension<T> expanded(T width, T height); + static Dimension<Integer> ofInt(int x, int y, int width, int height) { return new DimensionIntegerImpl(x, y, width, height); } diff --git a/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java b/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java new file mode 100644 index 0000000..ed51683 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/api/utils/OptionUtils.java @@ -0,0 +1,28 @@ +package dev.isxander.yacl.api.utils; + +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionGroup; +import dev.isxander.yacl.api.YetAnotherConfigLib; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class OptionUtils { + public static void consumeOptions(YetAnotherConfigLib yacl, Function<Option<?>, Boolean> consumer) { + for (ConfigCategory category : yacl.categories()) { + for (OptionGroup group : category.groups()) { + for (Option<?> option : group.options()) { + if (!consumer.apply(option)) return; + } + } + } + } + + public static void forEachOptions(YetAnotherConfigLib yacl, Consumer<Option<?>> consumer) { + consumeOptions(yacl, (opt) -> { + consumer.accept(opt); + return true; + }); + } +} diff --git a/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java b/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java index 2a8a519..7affbd4 100644 --- a/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java +++ b/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java @@ -1,8 +1,53 @@ package dev.isxander.yacl.gui; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.DrawableHelper; import net.minecraft.client.gui.Element; import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.sound.PositionedSoundInstance; +import net.minecraft.client.sound.SoundManager; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.sound.SoundEvents; public abstract class AbstractWidget implements Element, Drawable, Selectable { + protected final MinecraftClient client = MinecraftClient.getInstance(); + protected final TextRenderer textRenderer = client.textRenderer; + + public void tick() { + + } + + protected void drawButtonRect(MatrixStack matrices, int x1, int y1, int x2, int y2, boolean hovered) { + if (x1 > x2) { + int xx1 = x1; + x1 = x2; + x2 = xx1; + } + if (y1 > y2) { + int yy1 = y1; + y1 = y2; + y2 = yy1; + } + int width = x2 - x1; + int height = y2 - y1; + + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, ClickableWidget.WIDGETS_TEXTURE); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + int i = hovered ? 2 : 1; + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + DrawableHelper.drawTexture(matrices, x1, y1, 0, 0, 46 + i * 20, width / 2, height, 256, 256); + DrawableHelper.drawTexture(matrices, x1 + width / 2, y1, 0, 200 - width / 2f, 46 + i * 20, width / 2, height, 256, 256); + } + + public void playDownSound() { + MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } } diff --git a/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java new file mode 100644 index 0000000..6cb7090 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java @@ -0,0 +1,90 @@ +package dev.isxander.yacl.gui; + +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.OptionGroup; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.controllers.ControlWidget; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.util.math.MatrixStack; + +import java.util.List; + +public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry> { + + public OptionListWidget(ConfigCategory category, YACLScreen screen, MinecraftClient client, int width, int height) { + super(client, width / 3 * 2, height, 0, height, 22); + left = width - this.width; + right = width; + + for (OptionGroup group : category.groups()) { + if (!group.isRoot()) + addEntry(new GroupSeparatorEntry(group)); + for (Option<?> option : group.options()) { + addEntry(new OptionEntry(option.control().provideWidget(screen, null))); + } + } + } + + @Override + protected int getScrollbarPositionX() { + return left + super.getScrollbarPositionX(); + } + + public static abstract class Entry extends ElementListWidget.Entry<Entry> { + + } + + private static class OptionEntry extends Entry { + private final ControlWidget<?> widget; + + public OptionEntry(ControlWidget<?> widget) { + this.widget = widget; + } + + @Override + public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + widget.dim = Dimension.ofInt(x, y, entryWidth, 20); + + widget.render(matrices, mouseX, mouseY, tickDelta); + } + + @Override + public List<? extends Selectable> selectableChildren() { + return List.of(widget); + } + + @Override + public List<? extends Element> children() { + return List.of(widget); + } + } + + private static class GroupSeparatorEntry extends Entry { + private final OptionGroup group; + + public GroupSeparatorEntry(OptionGroup group) { + this.group = group; + } + + @Override + public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + drawCenteredText(matrices, textRenderer, group.name(), x + entryWidth / 2, y + entryHeight / 2 - textRenderer.fontHeight / 2, -1); + } + + @Override + public List<? extends Selectable> selectableChildren() { + return List.of(); + } + + @Override + public List<? extends Element> children() { + return List.of(); + } + } +} diff --git a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java new file mode 100644 index 0000000..97cc8ed --- /dev/null +++ b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java @@ -0,0 +1,150 @@ +package dev.isxander.yacl.gui; + +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.YetAnotherConfigLib; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.api.utils.OptionUtils; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class YACLScreen extends Screen { + public final YetAnotherConfigLib config; + public int currentCategoryIdx; + + private final Screen parent; + + public OptionListWidget optionList; + public final List<ButtonWidget> categoryButtons; + public ButtonWidget finishedSaveButton, cancelResetButton, undoButton; + + public YACLScreen(YetAnotherConfigLib config, Screen parent) { + 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; + Dimension<Integer> categoryDim = Dimension.ofInt(padding, padding, columnWidth - padding * 2, 20); + int idx = 0; + for (ConfigCategory category : config.categories()) { + ButtonWidget categoryWidget = new ButtonWidget( + categoryDim.x(), categoryDim.y(), + categoryDim.width(), categoryDim.height(), + category.name(), + (btn) -> changeCategory(categoryButtons.indexOf(btn)) + ); + if (idx == currentCategoryIdx) + categoryWidget.active = false; + categoryButtons.add(categoryWidget); |
