diff options
| author | Crendgrim <Crendgrim@users.noreply.github.com> | 2023-08-31 00:06:47 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-30 23:06:47 +0100 |
| commit | 554646dbd857e2fab1be8339ce8d0231ef2dbb4c (patch) | |
| tree | b55e6f1d282c18e743857bba3c9d5e4f226200ed | |
| parent | c11566072608683034864dbd4e0d3f5afa067537 (diff) | |
| download | YetAnotherConfigLib-554646dbd857e2fab1be8339ce8d0231ef2dbb4c.tar.gz YetAnotherConfigLib-554646dbd857e2fab1be8339ce8d0231ef2dbb4c.tar.bz2 YetAnotherConfigLib-554646dbd857e2fab1be8339ce8d0231ef2dbb4c.zip | |
Add dropdown controllers (#95)
22 files changed, 928 insertions, 6 deletions
diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/DropdownStringControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/DropdownStringControllerBuilder.java new file mode 100644 index 0000000..3f5fb33 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/DropdownStringControllerBuilder.java @@ -0,0 +1,18 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.DropdownStringControllerBuilderImpl; + +import java.util.List; + +public interface DropdownStringControllerBuilder extends StringControllerBuilder { + DropdownStringControllerBuilder values(List<String> values); + DropdownStringControllerBuilder values(String... values); + DropdownStringControllerBuilder allowEmptyValue(boolean allowEmptyValue); + DropdownStringControllerBuilder allowAnyValue(boolean allowAnyValue); + + + static DropdownStringControllerBuilder create(Option<String> option) { + return new DropdownStringControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/api/controller/ItemControllerBuilder.java b/common/src/main/java/dev/isxander/yacl3/api/controller/ItemControllerBuilder.java new file mode 100644 index 0000000..5a1f5fa --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/api/controller/ItemControllerBuilder.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.api.controller; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.impl.controller.ItemControllerBuilderImpl; +import net.minecraft.world.item.Item; + +public interface ItemControllerBuilder extends ControllerBuilder<Item> { + static ItemControllerBuilder create(Option<Item> option) { + return new ItemControllerBuilderImpl(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java b/common/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java index acbf338..deff6d7 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java +++ b/common/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java @@ -1,9 +1,12 @@ package dev.isxander.yacl3.config; import com.google.gson.*; +import dev.isxander.yacl3.gui.utils.ItemRegistryHelper; import dev.isxander.yacl3.impl.utils.YACLConstants; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; +import net.minecraft.world.item.Item; import java.awt.*; import java.io.IOException; @@ -66,6 +69,7 @@ public class GsonConfigInstance<T> extends ConfigInstance<T> { .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) .registerTypeHierarchyAdapter(Style.class, new Style.Serializer()) .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) + .registerTypeHierarchyAdapter(Item.class, new ItemTypeAdapter()) .serializeNulls() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create(); @@ -129,6 +133,17 @@ public class GsonConfigInstance<T> extends ConfigInstance<T> { return new JsonPrimitive(color.getRGB()); } } + public static class ItemTypeAdapter implements JsonSerializer<Item>, JsonDeserializer<Item> { + @Override + public Item deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + return ItemRegistryHelper.getItemFromName(jsonElement.getAsString()); + } + + @Override + public JsonElement serialize(Item item, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(BuiltInRegistries.ITEM.getKey(item).toString()); + } + } /** * Creates a builder for a GSON config instance. @@ -148,7 +163,8 @@ public class GsonConfigInstance<T> extends ConfigInstance<T> { .serializeNulls() .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) .registerTypeHierarchyAdapter(Style.class, new Style.Serializer()) - .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()); + .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) + .registerTypeHierarchyAdapter(Item.class, new ItemTypeAdapter()); private Builder(Class<T> configClass) { this.configClass = configClass; diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Dropdown.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Dropdown.java new file mode 100644 index 0000000..44239d5 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Dropdown.java @@ -0,0 +1,43 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory. + * <p> + * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.DropdownStringControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Dropdown { + /** + * The allowed values for the field. These will be shown in a dropdown + * that the user can filter and select from. + * <p> + * Only values in this list will be accepted and written to the config + * file, unless {@link #allow()} is set to ${@code ALLOW_ANY}. + * <p> + * Empty string is a valid value only if it appears in this list, or if + * {@link #allow()} is set to {@code ALLOW_EMPTY} or {@code ALLOW_ANY}. + */ + String[] values(); + + /** + * Whether to accept the empty string as a valid value if it does not + * already appear in {@link #values()}. If it already appears there, + * the value of this does not apply. + */ + boolean allowEmptyValue() default false; + + /** + * Whether to accept any string as a valid value. The list of strings + * supplied in {@link #values()} are only used as dropdown suggestions. + * Empty strings are still prohibited unless the empty string appears in + * {@link #values()} or {@link #allowEmptyValue()}. + */ + boolean allowAnyValue() default false; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ItemField.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ItemField.java new file mode 100644 index 0000000..84d2c7a --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/ItemField.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An option factory. + * <p> + * This creates a regular option with a + * {@link dev.isxander.yacl3.api.controller.ItemControllerBuilder} controller. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ItemField { +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DropdownImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DropdownImpl.java new file mode 100644 index 0000000..c487aab --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DropdownImpl.java @@ -0,0 +1,19 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.DropdownStringControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.Dropdown; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; + +public class DropdownImpl extends SimpleOptionFactory<Dropdown, String> { + @Override + protected ControllerBuilder<String> createController(Dropdown annotation, ConfigField<String> field, OptionAccess storage, Option<String> option) { + return DropdownStringControllerBuilder.create(option) + .values(annotation.values()) + .allowEmptyValue(annotation.allowEmptyValue()) + .allowAnyValue(annotation.allowAnyValue()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ItemFieldImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ItemFieldImpl.java new file mode 100644 index 0000000..2802f5c --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ItemFieldImpl.java @@ -0,0 +1,17 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.ItemControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.ItemField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import net.minecraft.world.item.Item; + +public class ItemFieldImpl extends SimpleOptionFactory<ItemField, Item> { + @Override + protected ControllerBuilder<Item> createController(ItemField annotation, ConfigField<Item> field, OptionAccess storage, Option<Item> option) { + return ItemControllerBuilder.create(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java index 4bcf5d6..4f6e3c7 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java @@ -31,6 +31,8 @@ public class OptionFactoryRegistry { registerOptionFactory(EnumCycler.class, new EnumCyclerImpl()); registerOptionFactory(StringField.class, new StringFieldImpl()); registerOptionFactory(ColorField.class, new ColorFieldImpl()); + registerOptionFactory(Dropdown.class, new DropdownImpl()); + registerOptionFactory(ItemField.class, new ItemFieldImpl()); registerOptionFactory(Label.class, new LabelImpl()); registerOptionFactory(ListGroup.class, new ListGroupImpl<>()); diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java index 1df9cfb..d308c23 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java @@ -1,12 +1,16 @@ package dev.isxander.yacl3.config.v2.impl.serializer; import com.google.gson.*; +import dev.isxander.yacl3.config.GsonConfigInstance; import dev.isxander.yacl3.config.v2.api.*; import dev.isxander.yacl3.config.v2.api.serializer.GsonConfigSerializerBuilder; +import dev.isxander.yacl3.gui.utils.ItemRegistryHelper; import dev.isxander.yacl3.impl.utils.YACLConstants; import dev.isxander.yacl3.platform.YACLPlatform; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; +import net.minecraft.world.item.Item; import org.quiltmc.parsers.json.JsonReader; import org.quiltmc.parsers.json.JsonWriter; import org.quiltmc.parsers.json.gson.GsonReader; @@ -129,6 +133,18 @@ public class GsonConfigSerializer<T> extends ConfigSerializer<T> { } } + public static class ItemTypeAdapter implements JsonSerializer<Item>, JsonDeserializer<Item> { + @Override + public Item deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + return ItemRegistryHelper.getItemFromName(jsonElement.getAsString()); + } + + @Override + public JsonElement serialize(Item item, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(BuiltInRegistries.ITEM.getKey(item).toString()); + } + } + public static class Builder<T> implements GsonConfigSerializerBuilder<T> { private final ConfigClassHandler<T> config; private Path path; @@ -139,6 +155,7 @@ public class GsonConfigSerializer<T> extends ConfigSerializer<T> { .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) .registerTypeHierarchyAdapter(Style.class, new Style.Serializer()) .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) + .registerTypeHierarchyAdapter(Item.class, new ItemTypeAdapter()) .setPrettyPrinting(); public Builder(ConfigClassHandler<T> config) { diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownController.java new file mode 100644 index 0000000..8251f9e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownController.java @@ -0,0 +1,80 @@ +package dev.isxander.yacl3.gui.controllers.dropdown; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.gui.controllers.string.IStringController; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class AbstractDropdownController<T> implements IStringController<T> { + protected final Option<T> option; + private final List<String> allowedValues; + public final boolean allowEmptyValue; + public final boolean allowAnyValue; + + /** + * Constructs a dropdown controller + * + * @param option bound option + * @param allowedValues possible values + */ + protected AbstractDropdownController(Option<T> option, List<String> allowedValues, boolean allowEmptyValue, boolean allowAnyValue) { + this.option = option; + this.allowedValues = allowedValues; + this.allowEmptyValue = allowEmptyValue; + this.allowAnyValue = allowAnyValue; + } + + protected AbstractDropdownController(Option<T> option) { + this(option, Collections.emptyList(), false, false); + } + + /** + * {@inheritDoc} + */ + @Override + public Option<T> option() { + return option; + } + + public List<String> getAllowedValues() { + return getAllowedValues(""); + } + public List<String> getAllowedValues(String inputField) { + List<String> values = new ArrayList<>(allowedValues); + if (allowEmptyValue && !values.contains("")) values.add(""); + if (allowAnyValue && !inputField.isBlank() && !allowedValues.contains(inputField)) { + values.add(inputField); + } + String currentValue = getString(); + if (allowAnyValue && !allowedValues.contains(currentValue)) { + values.add(currentValue); + } + return values; + } + + public boolean isValueValid(String value) { + if (value.isBlank()) return allowEmptyValue; + return allowAnyValue || getAllowedValues().contains(value); + } + + protected String getValidValue(String value) { + return getValidValue(value, 0); + } + protected String getValidValue(String value, int offset) { + if (offset == -1) return getString(); + + return getAllowedValues(value).stream() + .filter(val -> val.toLowerCase().contains(value.toLowerCase())) + .sorted((s1, s2) -> { + if (s1.startsWith(value) && !s2.startsWith(value)) return -1; + if (!s1.startsWith(value) && s2.startsWith(value)) return 1; + return s1.compareTo(s2); + }) + .skip(offset) + .findFirst() + .orElseGet(this::getString); + } + +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java new file mode 100644 index 0000000..f91fc41 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java @@ -0,0 +1,238 @@ +package dev.isxander.yacl3.gui.controllers.dropdown; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.vertex.PoseStack; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; +import dev.isxander.yacl3.gui.utils.GuiUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +import java.awt.Color; +import java.util.List; +import java.util.function.Consumer; + +public abstract class AbstractDropdownControllerElement<T, U> extends StringControllerElement { + public static final int MAX_SHOWN_NUMBER_OF_ITEMS = 7; + + private final AbstractDropdownController<T> dropdownController; + protected boolean dropdownVisible = false; + // Stores the current selection position. The item at this position in the dropdown list will be chosen as the + // accepted value when the element is closed. + protected int selectedIndex = 0; + // Stores a cached list of matching values + protected List<U> matchingValues = null; + + public AbstractDropdownControllerElement(AbstractDropdownController<T> control, YACLScreen screen, Dimension<Integer> dim) { + super(control, screen, dim, false); + this.dropdownController = control; + this.dropdownController.option.addListener((opt, val) -> this.matchingValues = this.computeMatchingValues()); + } + + public void showDropdown() { + dropdownVisible = true; + selectedIndex = 0; + } + + public void closeDropdown() { + dropdownVisible = false; + ensureValidValue(); + } + + public void ensureValidValue() { + inputField = dropdownController.getValidValue(inputField, selectedIndex); + this.matchingValues = this.computeMatchingValues(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) { + if (!dropdownVisible) { + showDropdown(); + doSelectAll(); + } + return true; + } + return false; + } + + @Override + public void setFocused(boolean focused) { + if (focused) { + doSelectAll(); + super.setFocused(true); + } else unfocus(); + } + + @Override + public void unfocus() { + closeDropdown(); + super.unfocus(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!inputFieldFocused) + return false; + if (dropdownVisible) { + switch (keyCode) { + case InputConstants.KEY_DOWN -> { + selectNextEntry(); + return true; + } + case InputConstants.KEY_UP -> { + selectPreviousEntry(); + return true; + } + case InputConstants.KEY_TAB -> { + if (Screen.hasShiftDown()) { + selectPreviousEntry(); + } else { + selectNextEntry(); + } + return true; + } + } + } else { + if (keyCode == InputConstants.KEY_RETURN || keyCode == InputConstants.KEY_NUMPADENTER) { + showDropdown(); + return true; + } + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + if (!dropdownVisible) { + showDropdown(); + } + return super.charTyped(chr, modifiers); + } + + @Override + protected int getValueColor() { + if (inputFieldFocused) { + if (!dropdownController.isValueValid(inputField)) { + return 0xFFF06080; + } + } + return super.getValueColor(); + } + + public void selectNextEntry() { + if (selectedIndex == getDropdownLength() - 1) { + selectedIndex = 0; + } else { + selectedIndex++; + } + } + + public void selectPreviousEntry() { + if (selectedIndex == 0) { + selectedIndex = getDropdownLength() - 1; + } else { + selectedIndex--; + } + } + + public int getDropdownLength() { + return matchingValues.size(); + } + + @Override + public boolean modifyInput(Consumer<StringBuilder> builder) { + boolean success = super.modifyInput(builder); + if (success) { + this.matchingValues = this.computeMatchingValues(); + } + return success; + } + + public abstract List<U> computeMatchingValues(); + + public boolean matchingValue(String value) { + return value.toLowerCase().contains(inputField.toLowerCase()); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (matchingValues == null) matchingValues = computeMatchingValues(); + + super.render(graphics, mouseX, mouseY, delta); + + if (inputFieldFocused && dropdownVisible) { + PoseStack matrices = graphics.pose(); + matrices.pushPose(); + matrices.translate(0, 0, 200); + renderDropdown(graphics); + matrices.popPose(); + } + } + + public void renderDropdown(GuiGraphics graphics) { + if (matchingValues.size() == 0) return; + // Limit the visible options to allow scrolling through the suggestion list + int begin = Math.max(0, selectedIndex - MAX_SHOWN_NUMBER_OF_ITEMS / 2); + int end = begin + MAX_SHOWN_NUMBER_OF_ITEMS; + if (end >= matchingValues.size()) { + end = matchingValues.size(); + begin = Math.max(0, end - MAX_SHOWN_NUMBER_OF_ITEMS); + } + + renderDropdownBackground(graphics, end - begin); + if (matchingValues.size() >= 1) { + // Highlight the currently selected element + graphics.setColor(0.0f, 0.0f, 0.0f, 0.5f); + int x = getDimension().x(); + int y = getDimension().yLimit() + 2 + getDimension().height() * (selectedIndex - begin); + graphics.fill(x, y, x + getDimension().width(), y + getDimension().height(), -1); + graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f); + graphics.renderOutline(x, y, getDimension().width(), getDimension().height(), -1); + + } + + int n = 1; + for (int i = begin; i < end; ++i) { + renderDropdownEntry(graphics, matchingValues.get(i), n); + ++n; + } + } + + protected int getDropdownEntryPadding() { + return 0; + } + + protected void renderDropdownEntry(GuiGraphics graphics, U value, int n) { + String entry = getString(value); + int color = -1; + Component text; + if (entry.isBlank()) { + text = Component.translatable("yacl.control.text.blank").withStyle(ChatFormatting.GRAY); + } else { + text = shortenString(entry); + } + graphics.drawString(textRenderer, text, getDimension().xLimit() - textRenderer.width(text) - getDecorationPadding() - getDropdownEntryPadding(), getTextY() + n * getDimension().height() + 2, color, true); + } + + public abstract String getString(U object); + + public Component shortenString(String value) { + return Component.literal(GuiUtils.shortenString(value, textRenderer, getDimension().width() - 20, "...")); + } + + public void renderDropdownBackground(GuiGraphics graphics, int numberOfItems) { + graphics.setColor(0.25f, 0.25f, 0.25f, 1.0f); + graphics.blit(Screen.BACKGROUND_LOCATION, getDimension().x(), getDimension().yLimit() + 2, 0, 0.0f, 0.0f, getDimension().width(), getDimension().height() * numberOfItems + 2, 32, 32); + graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f); + graphics.renderOutline(getDimension().x(), getDimension().yLimit() + 2, getDimension().width(), getDimension().height() * numberOfItems, -1); + } + + protected int getDecorationPadding() { + return super.getXPadding(); + } + +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringController.java new file mode 100644 index 0000000..fafc759 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringController.java @@ -0,0 +1,34 @@ +package dev.isxander.yacl3.gui.controllers.dropdown; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; + +import java.util.List; + +public class DropdownStringController extends AbstractDropdownController<String> { + + public DropdownStringController(Option<String> option, List<String> allowedValues, boolean allowEmptyValue, boolean allowAnyValue) { + super(option, allowedValues, allowEmptyValue, allowAnyValue); + } + + @Override + public String getString() { + return option().pendingValue(); + } + + @Override + public void setFromString(String value) { + option().requestSet(getValidValue(value)); + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) { + return new DropdownStringControllerElement(this, screen, widgetDimension); + } + +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringControllerElement.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringControllerElement.java new file mode 100644 index 0000000..615aada --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringControllerElement.java @@ -0,0 +1,31 @@ +package dev.isxander.yacl3.gui.controllers.dropdown; + +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.YACLScreen; + +import java.util.List; + +public class DropdownStringControllerElement extends AbstractDropdownControllerElement<String, String> { + private final DropdownStringController controller; + + public DropdownStringControllerElement(DropdownStringController control, YACLScreen screen, Dimension<Integer> dim) { + super(control, screen, dim); + this.controller = control; + } + + @Override + public List<String> computeMatchingValues() { + return controller.getAllowedValues(inputField).stream() + .filter(this::matchingValue) + .sorted((s1, s2) -> { + if (s1.startsWith(inputField) && !s2.startsWith(inputField)) return -1; + if (!s1.startsWith(inputField) && s2.startsWith(inputField)) return 1; + return s1.compareTo(s2); + }) + .toList(); + } + + public String getString(String object) { + return object; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemController.java b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemController.java new file mode 100644 index 0000000..ac903c7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemController.java @@ -0,0 +1,68 @@ +package dev.isxander.yacl3.gui.controllers.dropdown; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.utils.ItemRegistryHelper; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; + +/** + * Simple controller that simply runs the button action on press + * and renders a {@link} Text on the right. + */ +public class ItemController extends AbstractDropdownController<Item> { + + /** + * Constructs an item controller + * + * @param option bound option + */ + public ItemController(Option<Item> option) { + super(option); + } + + @Override + public String getString() { + return BuiltInRegistries.ITEM.getKey(option.pendingValue()).toString(); + } + + @Override + public void setFromString(String value) { + option.requestSet(ItemRegistryHelper.getItemFromName(value, option.pendingValue())); + } + + /** + * {@inheritDoc} + */ + @Override + public Component formatValue() { + return Component.literal(getString()); + } + + + @Override + public boolean isValueValid(String value) { + return ItemRegistryHelper.isRegisteredItem(value); + } + + @Override + protected String getValidValue(String value, int offset) { + return ItemRegistryHelper.getMatchingItemIdentifiers(value) + .skip(offset) |
