diff options
Diffstat (limited to 'src/client/java')
22 files changed, 1006 insertions, 75 deletions
diff --git a/src/client/java/dev/isxander/yacl/api/ConfigCategory.java b/src/client/java/dev/isxander/yacl/api/ConfigCategory.java index e9755dd..19c7f72 100644 --- a/src/client/java/dev/isxander/yacl/api/ConfigCategory.java +++ b/src/client/java/dev/isxander/yacl/api/ConfigCategory.java @@ -3,6 +3,7 @@ package dev.isxander.yacl.api; import com.google.common.collect.ImmutableList; import dev.isxander.yacl.impl.ConfigCategoryImpl; import dev.isxander.yacl.impl.OptionGroupImpl; +import dev.isxander.yacl.impl.utils.YACLConstants; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import org.apache.commons.lang3.Validate; @@ -76,6 +77,11 @@ public interface ConfigCategory { 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; } @@ -91,6 +97,9 @@ public interface ConfigCategory { 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; } diff --git a/src/client/java/dev/isxander/yacl/api/ListOption.java b/src/client/java/dev/isxander/yacl/api/ListOption.java new file mode 100644 index 0000000..895898c --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/ListOption.java @@ -0,0 +1,217 @@ +package dev.isxander.yacl.api; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import dev.isxander.yacl.impl.ListOptionImpl; +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.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * A list option that takes form as an option group for UX. + * You add this option through {@link ConfigCategory.Builder#group(OptionGroup)}. Do NOT add as an option. + * Users can add remove and reshuffle a list type. You can use any controller you wish, there are no dedicated + * controllers for list types. List options do not manipulate your list but get and set the list with a + * regular binding for simplicity. + * + * You may apply option flags like a normal option and collapse like a normal group, it is a merge of them both. + * Methods in this interface marked with {@link ApiStatus.Internal} should not be used, and could be subject to + * change at any time + * @param <T> + */ +public interface ListOption<T> extends OptionGroup, Option<List<T>> { + @Override + @NotNull ImmutableList<ListOptionEntry<T>> options(); + + /** + * Class of the entry type + */ + @NotNull Class<T> elementTypeClass(); + + @ApiStatus.Internal + ListOptionEntry<T> insertNewEntryToTop(); + + @ApiStatus.Internal + void insertEntry(int index, ListOptionEntry<?> entry); + + @ApiStatus.Internal + int indexOf(ListOptionEntry<?> entry); + + @ApiStatus.Internal + void removeEntry(ListOptionEntry<?> entry); + + @ApiStatus.Internal + void addRefreshListener(Runnable changedListener); + + static <T> Builder<T> createBuilder(Class<T> typeClass) { + return new Builder<>(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; + } + + /** + * 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; + } + + /** + * Sets the tooltip to be used by the list. It is displayed like a normal + * group when you hover over the name. Entries do not allow a tooltip. + * <p> + * 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<T> tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + /** + * 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; + } + + /** + * Sets the controller for the option. + * This is how you interact and change the options. + * + * @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; + } + + /** + * Sets the binding for the option. + * Used for default, getter and setter. + * + * @see Binding + */ + public Builder<T> binding(@NotNull Binding<List<T>> binding) { + Validate.notNull(binding, "`binding` cannot be null"); + + this.binding = binding; + return this; + } + + /** + * Sets the binding for the option. + * Shorthand of {@link Binding#generic(Object, Supplier, Consumer)} + * + * @param def default value of the option, used to reset + * @param getter should return the current value of the option + * @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; + } + + /** + * Sets if the option can be configured + * + * @see Option#available() + */ + public Builder<T> available(boolean available) { + this.available = available; + return this; + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * Dictates if the group should be collapsed by default. + * If not set, it will not be collapsed by default. + * + * @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); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/api/ListOptionEntry.java b/src/client/java/dev/isxander/yacl/api/ListOptionEntry.java new file mode 100644 index 0000000..e0a3424 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/api/ListOptionEntry.java @@ -0,0 +1,23 @@ +package dev.isxander.yacl.api; + +import com.google.common.collect.ImmutableSet; +import org.jetbrains.annotations.NotNull; + +public interface ListOptionEntry<T> extends Option<T> { + ListOption<T> parentGroup(); + + @Override + default @NotNull Class<T> typeClass() { + return parentGroup().elementTypeClass(); + } + + @Override + default @NotNull ImmutableSet<OptionFlag> flags() { + return parentGroup().flags(); + } + + @Override + default boolean available() { + return parentGroup().available(); + } +} diff --git a/src/client/java/dev/isxander/yacl/api/Option.java b/src/client/java/dev/isxander/yacl/api/Option.java index 772c816..394723f 100644 --- a/src/client/java/dev/isxander/yacl/api/Option.java +++ b/src/client/java/dev/isxander/yacl/api/Option.java @@ -71,12 +71,6 @@ public interface Option<T> { boolean changed(); /** - * If true, modifying this option recommends a restart. - */ - @Deprecated - boolean requiresRestart(); - - /** * Value in the GUI, ready to set the actual bound value or be undone. */ @NotNull T pendingValue(); @@ -109,6 +103,10 @@ public interface Option<T> { */ boolean isPendingValueDefault(); + default boolean canResetToDefault() { + return true; + } + /** * Adds a listener for when the pending value changes */ diff --git a/src/client/java/dev/isxander/yacl/api/OptionGroup.java b/src/client/java/dev/isxander/yacl/api/OptionGroup.java index 3364bdf..6cc6c7f 100644 --- a/src/client/java/dev/isxander/yacl/api/OptionGroup.java +++ b/src/client/java/dev/isxander/yacl/api/OptionGroup.java @@ -31,7 +31,7 @@ public interface OptionGroup { /** * List of all options in the group */ - @NotNull ImmutableList<Option<?>> options(); + @NotNull ImmutableList<? extends Option<?>> options(); /** * Dictates if the group should be collapsed by default. @@ -96,6 +96,9 @@ public interface OptionGroup { 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; } @@ -109,6 +112,9 @@ public interface OptionGroup { 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; } diff --git a/src/client/java/dev/isxander/yacl/api/utils/OptionUtils.java b/src/client/java/dev/isxander/yacl/api/utils/OptionUtils.java index ab46b5b..22032bd 100644 --- a/src/client/java/dev/isxander/yacl/api/utils/OptionUtils.java +++ b/src/client/java/dev/isxander/yacl/api/utils/OptionUtils.java @@ -1,9 +1,6 @@ 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 dev.isxander.yacl.api.*; import java.util.function.Consumer; import java.util.function.Function; @@ -16,9 +13,14 @@ 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; + if (group instanceof ListOption<?> list) { + if (consumer.apply(list)) return; + } else { + for (Option<?> option : group.options()) { + if (consumer.apply(option)) return; + } } + } } } diff --git a/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java b/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java index e97f46c..71a8e4e 100644 --- a/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java +++ b/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java @@ -32,12 +32,6 @@ public class CategoryListWidget extends ElementListWidgetExt<CategoryListWidget. RenderSystem.disableScissor(); } - public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { - for (CategoryEntry entry : children()) { - entry.postRender(matrices, mouseX, mouseY, delta); - } - } - @Override public int getRowWidth() { return Math.min(width - width / 10, 396); diff --git a/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java b/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java index 9fa01a7..36e0852 100644 --- a/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java +++ b/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java @@ -19,7 +19,7 @@ public class LowProfileButtonWidget extends ButtonWidget { @Override public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { - if (!isHovered()) { + if (!isHovered() || !active) { int j = this.active ? 0xFFFFFF : 0xA0A0A0; drawCenteredText(matrices, MinecraftClient.getInstance().textRenderer, this.getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, j | MathHelper.ceil(this.alpha * 255.0F) << 24); } else { diff --git a/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java index 976d796..8284b0e 100644 --- a/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java +++ b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java @@ -1,10 +1,9 @@ package dev.isxander.yacl.gui; import com.google.common.collect.ImmutableList; -import dev.isxander.yacl.api.ConfigCategory; -import dev.isxander.yacl.api.Option; -import dev.isxander.yacl.api.OptionGroup; +import dev.isxander.yacl.api.*; import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.controllers.ListEntryWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.MultilineText; import net.minecraft.client.font.TextRenderer; @@ -13,14 +12,11 @@ import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.screen.narration.NarrationPart; -import net.minecraft.client.gui.widget.ElementListWidget; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.function.Supplier; public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entry> { private final YACLScreen yaclScreen; @@ -33,6 +29,14 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr this.yaclScreen = screen; refreshOptions(); + + for (ConfigCategory category : screen.config.categories()) { + for (OptionGroup group : category.groups()) { + if (group instanceof ListOption<?> listOption) { + listOption.addRefreshListener(() -> refreshListEntries(listOption, category)); + } + } + } } public void refreshOptions() { @@ -40,6 +44,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr List<ConfigCategory> categories = new ArrayList<>(); if (yaclScreen.getCurrentCategoryIdx() == -1) { + // -1 = no category, search in progress, so use all categories for search categories.addAll(yaclScreen.config.categories()); } else { categories.add(yaclScreen.config.categories().get(yaclScreen.getCurrentCategoryIdx())); @@ -48,19 +53,19 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr for (ConfigCategory category : categories) { for (OptionGroup group : category.groups()) { - Supplier<Boolean> viewableSupplier; - GroupSeparatorEntry groupSeparatorEntry = null; + GroupSeparatorEntry groupSeparatorEntry; if (!group.isRoot()) { - groupSeparatorEntry = new GroupSeparatorEntry(group, yaclScreen); - viewableSupplier = groupSeparatorEntry::isExpanded; + groupSeparatorEntry = group instanceof ListOption<?> listOption + ? new ListGroupSeparatorEntry(listOption, yaclScreen) + : new GroupSeparatorEntry(group, yaclScreen); addEntry(groupSeparatorEntry); } else { - viewableSupplier = () -> true; + groupSeparatorEntry = null; } List<OptionEntry> optionEntries = new ArrayList<>(); for (Option<?> option : group.options()) { - OptionEntry entry = new OptionEntry(option, category, group, option.controller().provideWidget(yaclScreen, Dimension.ofInt(getRowLeft(), 0, getRowWidth(), 20)), viewableSupplier); + OptionEntry entry = new OptionEntry(option, category, group, groupSeparatorEntry, option.controller().provideWidget(yaclScreen, getDefaultEntryPosition())); addEntry(entry); optionEntries.add(entry); } @@ -76,6 +81,30 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr resetSmoothScrolling(); } + 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); + + if (groupSeparator == null) + return; + + for (OptionEntry entry : groupSeparator.optionEntries) + super.removeEntry(entry); + + groupSeparator.optionEntries.clear(); + Entry lastEntry = groupSeparator; + for (ListOptionEntry<?> listOptionEntry : listOption.options()) { + OptionEntry optionEntry = new OptionEntry(listOptionEntry, category, listOption, groupSeparator, listOptionEntry.controller().provideWidget(yaclScreen, getDefaultEntryPosition())); + addEntryBelow(lastEntry, optionEntry); + groupSeparator.optionEntries.add(optionEntry); + lastEntry = optionEntry; + } + } + + public Dimension<Integer> getDefaultEntryPosition() { + return Dimension.ofInt(getRowLeft(), 0, getRowWidth(), 20); + } + public void expandAllGroups() { for (Entry entry : super.children()) { if (entry instanceof GroupSeparatorEntry groupSeparatorEntry) { @@ -153,6 +182,40 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr return viewableChildren; } + public void addEntry(int index, Entry entry) { + super.children().add(index, entry); + recacheViewableChildren(); + } + + public void addEntryBelow(Entry below, Entry entry) { + int idx = super.children().indexOf(below) + 1; + + if (idx == 0) + throw new IllegalStateException("The entry to insert below does not exist!"); + + addEntry(idx, entry); + } + + public void addEntryBelowWithoutScroll(Entry below, Entry entry) { + double d = (double)this.getMaxScroll() - this.getScrollAmount(); + addEntryBelow(below, entry); + setScrollAmount(getMaxScroll() - d); + } + + @Override + public boolean removeEntryWithoutScrolling(Entry entry) { + boolean ret = super.removeEntryWithoutScrolling(entry); + recacheViewableChildren(); + return ret; + } + + @Override + public boolean removeEntry(Entry entry) { + boolean ret = super.removeEntry(entry); + recacheViewableChildren(); + return ret; + } + public abstract class Entry extends ElementListWidgetExt.Entry<Entry> { public boolean isViewable() { return true; @@ -168,25 +231,28 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr public final ConfigCategory category; public final OptionGroup group; + public final @Nullable GroupSeparatorEntry groupSeparatorEntry; + public final AbstractWidget widget; - private final Supplier<Boolean> viewableSupplier; private final TextScaledButtonWidget resetButton; private final String categoryName; private final String groupName; - private OptionEntry(Option<?> option, ConfigCategory category, OptionGroup group, AbstractWidget widget, Supplier<Boolean> viewableSupplier) { + public OptionEntry(Option<?> option, ConfigCategory category, OptionGroup group, @Nullable GroupSeparatorEntry groupSeparatorEntry, AbstractWidget widget) { this.option = option; this.category = category; this.group = group; - this.widget = widget; - this.viewableSupplier = viewableSupplier; + this.groupSeparatorEntry = groupSeparatorEntry; + if (option instanceof ListOptionEntry<?> listOptionEntry) + this.widget = new ListEntryWidget(yaclScreen, listOptionEntry, widget); + else this.widget = widget; this.categoryName = category.name().getString().toLowerCase(); this.groupName = group.name().getString().toLowerCase(); - if (this.widget.canReset()) { - this.widget.setDimension(this.widget.getDimension().expanded(-21, 0)); - this.resetButton = new TextScaledButtonWidget(widget.getDimension().xLimit() + 1, -50, 20, 20, 2f, Text.of("\u21BB"), button -> { + if (option.canResetToDefault() && this.widget.canReset()) { + this.widget.setDimension(this.widget.getDimension().expanded(-20, 0)); + this.resetButton = new TextScaledButtonWidget(widget.getDimension().xLimit(), -50, 20, 20, 2f, Text.of("\u21BB"), button -> { option.requestSetDefault(); }); option.addListener((opt, val) -> this.resetButton.active = !opt.isPendingValueDefault() && opt.available()); @@ -231,7 +297,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr @Override public boolean isViewable() { String query = yaclScreen.searchFieldWidget.getText(); - return viewableSupplier.get() + return (groupSeparatorEntry == null || groupSeparatorEntry.isExpanded()) && (yaclScreen.searchFieldWidget.isEmpty() || (!singleCategory && categoryName.contains(query)) || groupName.contains(query) @@ -261,18 +327,18 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr } public class GroupSeparatorEntry extends Entry { - private final OptionGroup group; - private final MultilineText wrappedName; - private final MultilineText wrappedTooltip; + protected final OptionGroup group; + protected final MultilineText wrappedName; + protected final MultilineText wrappedTooltip; - private final LowProfileButtonWidget expandMinimizeButton; + protected final LowProfileButtonWidget expandMinimizeButton; - private final Screen screen; - private final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + protected final Screen screen; + protected final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; - private boolean groupExpanded; + protected boolean groupExpanded; - private List<OptionEntry> optionEntries; + protected List<OptionEntry> optionEntries; private int y; @@ -282,10 +348,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr this.wrappedName = MultilineText.create(textRenderer, group.name(), getRowWidth() - 45); this.wrappedTooltip = MultilineText.create(textRenderer, group.tooltip(), screen.width / 3 * 2 - 10); this.groupExpanded = !group.collapsed(); - this.expandMinimizeButton = new LowProfileButtonWidget(0, 0, 20, 20, Text.empty(), btn -> { - setExpanded(!isExpanded()); - recacheViewableChildren(); - }); + this.expandMinimizeButton = new LowProfileButtonWidget(0, 0, 20, 20, Text.empty(), btn -> onExpandButtonPress()); updateExpandMinimizeText(); } @@ -293,8 +356,10 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { this.y = y; + int buttonY = y + entryHeight / 2 - expandMinimizeButton.getHeight() / 2 + 1; + + expandMinimizeButton.setY(buttonY); expandMinimizeButton.setX(x); - expandMinimizeButton.setY(y + entryHeight / 2 - expandMinimizeButton.getHeight() / 2); expandMinimizeButton.render(matrices, mouseX, mouseY, tickDelta); wrappedName.drawCenterWithShadow(matrices, x + entryWidth / 2, y + getYPadding()); @@ -316,7 +381,12 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr updateExpandMinimizeText(); } - private void updateExpandMinimizeText() { + protected void onExpandButtonPress() { + setExpanded(!isExpanded()); + recacheViewableChildren(); + } + + protected void updateExpandMinimizeText() { expandMinimizeButton.setMessage(Text.of(isExpanded() ? "▼" : "▶")); } @@ -349,6 +419,7 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr @Override public void appendNarrations(NarrationMessageBuilder builder) { builder.put(NarrationPart.TITLE, group.name()); + builder.put(NarrationPart.HINT, group.tooltip()); } }); } @@ -358,4 +429,72 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr return ImmutableList.of(expandMinimizeButton); } } + + public class ListGroupSeparatorEntry extends GroupSeparatorEntry { + private final ListOption<?> listOption; + private final TextScaledButtonWidget resetListButton; + private final TooltipButtonWidget addListButton; + + private ListGroupSeparatorEntry(ListOption<?> group, Screen screen) { + super(group, screen); + this.listOption = group; + + this.resetListButton = new TextScaledButtonWidget(getRowRight() - 20, -50, 20, 20, 2f, Text.of("\u21BB"), button -> { + group.requestSetDefaul |
