aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/java/dev/isxander/yacl/api/LabelOption.java20
-rw-r--r--src/client/java/dev/isxander/yacl/api/Option.java1
-rw-r--r--src/client/java/dev/isxander/yacl/gui/OptionListWidget.java82
-rw-r--r--src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java4
-rw-r--r--src/client/java/dev/isxander/yacl/impl/LabelOptionImpl.java113
-rw-r--r--src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java2
-rw-r--r--src/main/resources/assets/yet-another-config-lib/lang/en_us.json1
-rw-r--r--src/testmod/java/dev/isxander/yacl/test/config/GuiTest.java129
8 files changed, 239 insertions, 113 deletions
diff --git a/src/client/java/dev/isxander/yacl/api/LabelOption.java b/src/client/java/dev/isxander/yacl/api/LabelOption.java
new file mode 100644
index 0000000..a1bbe28
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/api/LabelOption.java
@@ -0,0 +1,20 @@
+package dev.isxander.yacl.api;
+
+import dev.isxander.yacl.impl.LabelOptionImpl;
+import net.minecraft.text.Text;
+
+/**
+ * A label option is an easier way of creating a label with a {@link dev.isxander.yacl.gui.controllers.LabelController}.
+ * This option is immutable and cannot be disabled. Tooltips are supported through
+ * {@link Text} styling.
+ */
+public interface LabelOption extends Option<Text> {
+ Text label();
+
+ /**
+ * Creates a new label option with the given label.
+ */
+ static LabelOption create(Text label) {
+ return new LabelOptionImpl(label);
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/api/Option.java b/src/client/java/dev/isxander/yacl/api/Option.java
index 406931f..9b4ff7b 100644
--- a/src/client/java/dev/isxander/yacl/api/Option.java
+++ b/src/client/java/dev/isxander/yacl/api/Option.java
@@ -136,6 +136,7 @@ public interface Option<T> {
*
* @param tooltipGetter function to get tooltip depending on value {@link Builder#build()}.
*/
+ @SuppressWarnings("unchecked")
Builder<T> tooltip(@NotNull Function<T, Text>... tooltipGetter);
/**
diff --git a/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java
index c18597f..674fc56 100644
--- a/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java
+++ b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java
@@ -3,8 +3,6 @@ package dev.isxander.yacl.gui;
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;
@@ -16,6 +14,7 @@ import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
import net.minecraft.client.gui.screen.narration.NarrationPart;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -27,7 +26,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
private ImmutableList<Entry> viewableChildren;
public OptionListWidget(YACLScreen screen, MinecraftClient client, int width, int height) {
- super(client, width / 3, 0, width / 3 * 2, height, true);
+ super(client, width / 3, 0, width / 3 * 2 + 1, height, true);
this.yaclScreen = screen;
refreshOptions();
@@ -65,7 +64,17 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
groupSeparatorEntry = null;
}
- List<OptionEntry> optionEntries = new ArrayList<>();
+ List<Entry> optionEntries = new ArrayList<>();
+
+ // add empty entry to make sure users know it's empty not just bugging out
+ if (groupSeparatorEntry instanceof ListGroupSeparatorEntry listGroupSeparatorEntry) {
+ if (listGroupSeparatorEntry.listOption.options().isEmpty()) {
+ EmptyListLabel emptyListLabel = new EmptyListLabel(listGroupSeparatorEntry, category);
+ addEntry(emptyListLabel);
+ optionEntries.add(emptyListLabel);
+ }
+ }
+
for (Option<?> option : group.options()) {
OptionEntry entry = new OptionEntry(option, category, group, groupSeparatorEntry, option.controller().provideWidget(yaclScreen, getDefaultEntryDimension()));
addEntry(entry);
@@ -73,7 +82,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
}
if (groupSeparatorEntry != null) {
- groupSeparatorEntry.setOptionEntries(optionEntries);
+ groupSeparatorEntry.setChildEntries(optionEntries);
}
}
}
@@ -85,20 +94,22 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
private void refreshListEntries(ListOption<?> listOption, ConfigCategory category) {
// 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);
+ ListGroupSeparatorEntry groupSeparator = super.children().stream().filter(e -> e instanceof ListGroupSeparatorEntry gs && gs.group == listOption).map(ListGroupSeparatorEntry.class::cast).findAny().orElse(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)
+ for (Entry entry : groupSeparator.childEntries)
super.removeEntry(entry);
- groupSeparator.optionEntries.clear();
+ groupSeparator.childEntries.clear();
// if no entries, below loop won't run where addEntryBelow() recaches viewable children
if (listOption.options().isEmpty()) {
- recacheViewableChildren();
+ EmptyListLabel emptyListLabel;
+ addEntryBelow(groupSeparator, emptyListLabel = new EmptyListLabel(groupSeparator, category));
+ groupSeparator.childEntries.add(emptyListLabel);
return;
}
@@ -106,7 +117,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
for (ListOptionEntry<?> listOptionEntry : listOption.options()) {
OptionEntry optionEntry = new OptionEntry(listOptionEntry, category, listOption, groupSeparator, listOptionEntry.controller().provideWidget(yaclScreen, getDefaultEntryDimension()));
addEntryBelow(lastEntry, optionEntry);
- groupSeparator.optionEntries.add(optionEntry);
+ groupSeparator.childEntries.add(optionEntry);
lastEntry = optionEntry;
}
}
@@ -304,7 +315,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
@Override
public boolean isViewable() {
- String query = yaclScreen.searchFieldWidget.getText();
+ String query = yaclScreen.searchFieldWidget.getQuery();
return (groupSeparatorEntry == null || groupSeparatorEntry.isExpanded())
&& (yaclScreen.searchFieldWidget.isEmpty()
|| (!singleCategory && categoryName.contains(query))
@@ -346,7 +357,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
protected boolean groupExpanded;
- protected List<OptionEntry> optionEntries;
+ protected List<Entry> childEntries = new ArrayList<>();
private int y;
@@ -401,13 +412,14 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
expandMinimizeButton.setMessage(Text.of(isExpanded() ? "▼" : "▶"));
}
- public void setOptionEntries(List<OptionEntry> optionEntries) {
- this.optionEntries = optionEntries;
+ public void setChildEntries(List<? extends Entry> childEntries) {
+ this.childEntries.clear();
+ this.childEntries.addAll(childEntries);
}
@Override
public boolean isViewable() {
- return yaclScreen.searchFieldWidget.isEmpty() || optionEntries.stream().anyMatch(OptionEntry::isViewable);
+ return yaclScreen.searchFieldWidget.isEmpty() || childEntries.stream().anyMatch(Entry::isViewable);
}
@Override
@@ -506,4 +518,44 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
return ImmutableList.of(expandMinimizeButton, addListButton, resetListButton);
}
}
+
+ public class EmptyListLabel extends Entry {
+ private final ListGroupSeparatorEntry parent;
+ private final String groupName;
+ private final String categoryName;
+
+ public EmptyListLabel(ListGroupSeparatorEntry parent, ConfigCategory category) {
+ this.parent = parent;
+ this.groupName = parent.group.name().getString().toLowerCase();
+ this.categoryName = category.name().getString().toLowerCase();
+ }
+
+ @Override
+ public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
+ drawCenteredText(matrices, MinecraftClient.getInstance().textRenderer, Text.translatable("yacl.list.empty").formatted(Formatting.DARK_GRAY, Formatting.ITALIC), x + entryWidth / 2, y, -1);
+ }
+
+ @Override
+ public boolean isViewable() {
+ String query = yaclScreen.searchFieldWidget.getQuery();
+ return parent.isExpanded() && (yaclScreen.searchFieldWidget.isEmpty()
+ || (!singleCategory && categoryName.contains(query))
+ || groupName.contains(query));
+ }
+
+ @Override
+ public int getItemHeight() {
+ return 11;
+ }
+
+ @Override
+ public List<? extends Element> children() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public List<? extends Selectable> selectableChildren() {
+ return ImmutableList.of();
+ }
+ }
}
diff --git a/src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java b/src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java
index 5b7c9dc..3cfe75e 100644
--- a/src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java
+++ b/src/client/java/dev/isxander/yacl/gui/SearchFieldWidget.java
@@ -48,6 +48,10 @@ public class SearchFieldWidget extends TextFieldWidget {
yaclScreen.categoryList.setScrollAmount(0);
}
+ public String getQuery() {
+ return getText().toLowerCase();
+ }
+
public boolean isEmpty() {
return isEmpty;
}
diff --git a/src/client/java/dev/isxander/yacl/impl/LabelOptionImpl.java b/src/client/java/dev/isxander/yacl/impl/LabelOptionImpl.java
new file mode 100644
index 0000000..314c2ad
--- /dev/null
+++ b/src/client/java/dev/isxander/yacl/impl/LabelOptionImpl.java
@@ -0,0 +1,113 @@
+package dev.isxander.yacl.impl;
+
+import com.google.common.collect.ImmutableSet;
+import dev.isxander.yacl.api.*;
+import dev.isxander.yacl.gui.controllers.LabelController;
+import net.minecraft.text.Text;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.BiConsumer;
+
+public class LabelOptionImpl implements LabelOption {
+ private final Text label;
+ private final Text name = Text.literal("Label Option");
+ private final Text tooltip = Text.empty();
+ private final LabelController labelController;
+ private final Binding<Text> binding;
+
+ public LabelOptionImpl(Text label) {
+ this.label = label;
+ this.labelController = new LabelController(this);
+ this.binding = Binding.immutable(label);
+ }
+
+ @Override
+ public Text label() {
+ return label;
+ }
+
+ @Override
+ public @NotNull Text name() {
+ return name;
+ }
+
+ @Override
+ public @NotNull Text tooltip() {
+ return tooltip;
+ }
+
+ @Override
+ public @NotNull Controller<Text> controller() {
+ return labelController;
+ }
+
+ @Override
+ public @NotNull Binding<Text> binding() {
+ return binding;
+ }
+
+ @Override
+ public boolean available() {
+ return true;
+ }
+
+ @Override
+ public void setAvailable(boolean available) {
+ throw new UnsupportedOperationException("Label options cannot be disabled.");
+ }
+
+ @Override
+ public @NotNull Class<Text> typeClass() {
+ return Text.class;
+ }
+
+ @Override
+ public @NotNull ImmutableSet<OptionFlag> flags() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public boolean changed() {
+ return false;
+ }
+
+ @Override
+ public @NotNull Text pendingValue() {
+ return label;
+ }
+
+ @Override
+ public void requestSet(Text value) {
+
+ }
+
+ @Override
+ public boolean applyValue() {
+ return false;
+ }
+
+ @Override
+ public void forgetPendingValue() {
+
+ }
+
+ @Override
+ public void requestSetDefault() {
+
+ }
+
+ @Override
+ public boolean isPendingValueDefault() {
+ return true;
+ }
+
+ @Override
+ public boolean canResetToDefault() {
+ return false;
+ }
+
+ @Override
+ public void addListener(BiConsumer<Option<Text>, Text> changedListener) {
+
+ }
+}
diff --git a/src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java b/src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java
index 1924205..fb74601 100644
--- a/src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java
+++ b/src/client/java/dev/isxander/yacl/impl/ListOptionImpl.java
@@ -93,7 +93,7 @@ public final class ListOptionImpl<T> implements ListOption<T> {
}
@Override
- public ImmutableList<T> pendingValue() {
+ public @NotNull ImmutableList<T> pendingValue() {
return ImmutableList.copyOf(entries.stream().map(Option::pendingValue).toList());
}
diff --git a/src/main/resources/assets/yet-another-config-lib/lang/en_us.json b/src/main/resources/assets/yet-another-config-lib/lang/en_us.json
index 292864f..32621e9 100644
--- a/src/main/resources/assets/yet-another-config-lib/lang/en_us.json
+++ b/src/main/resources/assets/yet-another-config-lib/lang/en_us.json
@@ -20,6 +20,7 @@
"yacl.list.move_down": "Move down",
"yacl.list.remove": "Remove",
"yacl.list.add_top": "New entry",
+ "yacl.list.empty": "List is empty",
"yacl.restart.title": "Config requires restart!",
"yacl.restart.message": "One or more options needs you to restart the game to apply the changes.",
diff --git a/src/testmod/java/dev/isxander/yacl/test/config/GuiTest.java b/src/testmod/java/dev/isxander/yacl/test/config/GuiTest.java
index 4965150..1881e3c 100644
--- a/src/testmod/java/dev/isxander/yacl/test/config/GuiTest.java
+++ b/src/testmod/java/dev/isxander/yacl/test/config/GuiTest.java
@@ -36,20 +36,13 @@ public class GuiTest {
.controller(ActionController::new)
.action((screen, opt) -> MinecraftClient.getInstance().setScreen(getFullTestSuite(screen)))
.build())
- .option(ButtonOption.createBuilder()
- .name(Text.of("Basic Wiki Suite"))
- .controller(ActionController::new)
- .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getWikiBasic(screen)))
- .build())
- .option(ButtonOption.createBuilder()
- .name(Text.of("Group Wiki Suite"))
- .controller(ActionController::new)
- .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getWikiGroups(screen)))
- .build())
- .option(ButtonOption.createBuilder()
- .name(Text.of("Unavailable Test Suite"))
- .controller(ActionController::new)
- .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getDisabledTest(screen)))
+ .group(OptionGroup.createBuilder()
+ .name(Text.of("Wiki"))
+ .option(ButtonOption.createBuilder()
+ .name(Text.of("Get Started"))
+ .controller(ActionController::new)
+ .action((screen, opt) -> MinecraftClient.getInstance().setScreen(getWikiGetStarted(screen)))
+ .build())
.build())
.build())
)
@@ -76,7 +69,6 @@ public class GuiTest {
)
.controller(BooleanController::new)
.flag(OptionFlag.GAME_RESTART)
- .available(false)
.build())
.option(Option.createBuilder(boolean.class)
.name(Text.of("Custom Boolean Toggle"))
@@ -89,7 +81,7 @@ public class GuiTest {
.controller(opt -> new BooleanController(opt, state -> state ? Text.of("Amazing") : Text.of("Not Amazing"), true))
.build())
.option(Option.createBuilder(boolean.class)
- .name(Text.of("Tick Box aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
+ .name(Text.of("Tick Box"))
.tooltip(Text.of("There are even alternate methods of displaying the same data type!"))
.binding(
defaults.tickbox,
@@ -102,7 +94,7 @@ public class GuiTest {
.group(OptionGroup.createBuilder()
.name(Text.of("Slider Controllers"))
.option(Option.createBuilder(int.class)
- .name(Text.of("Int Slider that is cut off because the slider"))
+ .name(Text.of("Int Slider"))
.instant(true)
.binding(
defaults.intSlider,
@@ -219,16 +211,14 @@ public class GuiTest {
.action((screen, opt) -> SystemToast.add(MinecraftClient.getInstance().getToastManager(), SystemToast.Type.TUTORIAL_HINT, Text.of("Button Pressed"), Text.of("Button option was invoked!")))
.controller(ActionController::new)
.build())
- .option(Option.createBuilder(Text.class)
- .binding(Binding.immutable(Text.empty()
+ .option(LabelOption.create(
+ Text.empty()
.append(Text.literal("a").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("a")))))
.append(Text.literal("b").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("b")))))
.append(Text.literal("c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("c")))))
.append(Text.literal("e").styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("e")))))
- .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev")))
- ))
- .controller(LabelController::new)
- .build())
+ .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://isxander.dev"))))
+ )
.build())
.group(OptionGroup.createBuilder()
.name(Text.of("Minecraft Bindings"))
@@ -410,81 +400,26 @@ public class GuiTest {
.generateScreen(parent);
}
- private static Screen getDisabledTest(Screen parent) {
- return YetAnotherConfigLib.create(ExampleConfig.INSTANCE, (defaults, config, builder) -> builder
- .title(Text.empty())
- .category(ConfigCategory.createBuilder()
- .name(Text.of("Disabled Test"))
- .option(Option.createBuilder(int.class)
- .name(Text.of("Slider"))
- .binding(Binding.immutable(0))
- .controller(opt -> new IntegerSliderController(opt, 0, 5, 1))
- .available(false)
- .build())
- .option(Option.createBuilder(boolean.class)
- .name(Text.of("Tick Box"))
- .binding(Binding.immutable(true))
- .controller(TickBoxController::new)
- .available(false)
- .build())
- .option(Option.createBuilder(boolean.class)
- .name(Text.of("Tick Box (Enabled)"))
- .binding(Binding.immutable(true))
- .controller(TickBoxController::new)
- .build())
- .option(Option.createBuilder(String.class)
- .name(Text.of("Text Field"))
- .binding(Binding.immutable("hi"))
- .controller(StringController::new)
- .available(false)
- .build())
- .build())
- )
- .generateScreen(parent);
- }
+ private static boolean myBooleanOption = true;
- private static Screen getWikiBasic(Screen parent) {
- return YetAnotherConfigLib.create(ExampleConfig.INSTANCE, (defaults, config, builder) -> builder
- .title(Text.of("Mod Name"))
- .category(ConfigCategory.createBuilder()
- .name(Text.of("My Category"))
- .tooltip(Text.of("This displays when you hover over a category button")) // optional
- .option(Option.createBuilder(boolean.class)
- .name(Text.of("My Boolean Option"))
- .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional
- .binding(
- defaults.booleanToggle, // default
- () -> config.booleanToggle, // getter
- newValue -> config.booleanToggle = newValue // setter
- )
- .controller(BooleanController::new)
- .build())
- .build())
- )
- .generateScreen(parent);
- }
-
- private static Screen getWikiGroups(Screen parent) {
- return YetAnotherConfigLib.create(ExampleConfig.INSTANCE, (defaults, config, builder) -> builder
- .title(Text.of("Mod Name"))
- .category(ConfigCategory.createBuilder()
- .name(Text.of("My Category"))
- .tooltip(Text.of("This displays when you hover over a category button")) // optional
- .group(OptionGroup.createBuilder()
- .name(Text.of("Option Group"))
- .option(Option.createBuilder(boolean.class)
- .name(Text.of("My Boolean Option"))
- .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional
- .binding(
- defaults.booleanToggle, // default
- () -> config.booleanToggle, // getter
- newValue -> config.booleanToggle = newValue // setter
- )
- .controller(BooleanController::new)
- .build())
- .build())
- .build())
- )
+ private static Screen getWikiGetStarted(Screen parent) {
+ return YetAnotherConfigLib.createBuilder()
+ .title(Text.literal("Used for narration. Could be used to render a title in the future."))
+ .category(ConfigCategory.createBuilder()
+ .name(Text.literal("Name of the category"))
+ .tooltip(Text.literal("This text will appear as a tooltip when you hover or focus the button with Tab. There is no need to add \n to wrap as YACL will do it for you."))
+ .group(OptionGroup.createBuilder()
+ .name(Text.literal("Name of the group"))
+ .tooltip(Text.literal("This text will appear when you hover over the name or focus on the collapse button with Tab."))
+ .option(Option.createBuilder(boolean.class)
+ .name(Text.literal("Boolean Option"))
+ .tooltip(Text.literal("This text will appear as a tooltip when you hover over the option."))
+ .binding(true, () -> myBooleanOption, newVal -> myBooleanOption = newVal)
+ .controller(TickBoxController::new)
+ .build())
+ .build())
+ .build())
+ .build()
.generateScreen(parent);
}
}