aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown
diff options
context:
space:
mode:
authorisxander <xander@isxander.dev>2024-04-11 18:43:06 +0100
committerisxander <xander@isxander.dev>2024-04-11 18:43:06 +0100
commit04fe933f4c24817100f3101f088accf55a621f8a (patch)
treefeff94ca3ab4484160e69a24f4ee38522381950e /src/main/java/dev/isxander/yacl3/gui/controllers/dropdown
parent831b894fdb7fe3e173d81387c8f6a2402b8ccfa9 (diff)
downloadYetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.tar.gz
YetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.tar.bz2
YetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.zip
Extremely fragile and broken multiversion build with stonecutter
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/gui/controllers/dropdown')
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownController.java87
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java248
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringController.java34
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringControllerElement.java31
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/EnumDropdownController.java92
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/EnumDropdownControllerElement.java25
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemController.java68
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java87
8 files changed, 672 insertions, 0 deletions
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownController.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownController.java
new file mode 100644
index 0000000..b913d15
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownController.java
@@ -0,0 +1,87 @@
+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, List<String> allowedValues) {
+ this(option, allowedValues, false, false);
+ }
+
+ protected AbstractDropdownController(Option<T> option) {
+ this(option, Collections.emptyList());
+ }
+
+ /**
+ * {@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();
+
+ String valueLowerCase = value.toLowerCase();
+ return getAllowedValues(value).stream()
+ .filter(val -> val.toLowerCase().contains(valueLowerCase))
+ .sorted((s1, s2) -> {
+ String s1LowerCase = s1.toLowerCase();
+ String s2LowerCase = s2.toLowerCase();
+ if (s1LowerCase.startsWith(valueLowerCase) && !s2LowerCase.startsWith(valueLowerCase)) return -1;
+ if (!s1LowerCase.startsWith(valueLowerCase) && s2LowerCase.startsWith(valueLowerCase)) return 1;
+ return s1.compareTo(s2);
+ })
+ .skip(offset)
+ .findFirst()
+ .orElseGet(this::getString);
+ }
+
+}
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java
new file mode 100644
index 0000000..49e0c0e
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java
@@ -0,0 +1,248 @@
+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.isEmpty()) 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.isEmpty()) {
+ // 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(
+ /*? if >1.20.4 {*//*
+ Screen.MENU_BACKGROUND,
+ *//*?} else {*/
+ 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/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringController.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringController.java
new file mode 100644
index 0000000..fafc759
--- /dev/null
+++ b/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/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringControllerElement.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownStringControllerElement.java
new file mode 100644
index 0000000..615aada
--- /dev/null
+++ b/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/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/EnumDropdownController.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/EnumDropdownController.java
new file mode 100644
index 0000000..8e6e0e6
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/EnumDropdownController.java
@@ -0,0 +1,92 @@
+package dev.isxander.yacl3.gui.controllers.dropdown;
+
+import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.api.utils.Dimension;
+import dev.isxander.yacl3.gui.AbstractWidget;
+import dev.isxander.yacl3.gui.YACLScreen;
+import net.minecraft.network.chat.Component;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+public class EnumDropdownController<E extends Enum<E>> extends AbstractDropdownController<E> {
+ /**
+ * The function used to convert enum constants to strings used for display, suggestion, and validation. Defaults to {@link Enum#toString}.
+ */
+ protected final ValueFormatter<E> formatter;
+
+ public EnumDropdownController(Option<E> option, ValueFormatter<E> formatter) {
+ super(option, Arrays.stream(option.pendingValue().getDeclaringClass().getEnumConstants()).map(formatter::format).map(Component::getString).toList());
+ this.formatter = formatter;
+ }
+
+ @Override
+ public String getString() {
+ return formatter.format(option().pendingValue()).getString();
+ }
+
+ @Override
+ public void setFromString(String value) {
+ option().requestSet(getEnumFromString(value));
+ }
+
+ /**
+ * Searches through enum constants for one whose {@link #formatter} result equals {@code value}
+ *
+ * @return The enum constant associated with the {@code value} or the pending value if none are found
+ * @implNote The return value of {@link #formatter} on each enum constant should be unique in order to ensure accuracy
+ */
+ private E getEnumFromString(String value) {
+ value = value.toLowerCase();
+ for (E constant : option().pendingValue().getDeclaringClass().getEnumConstants()) {
+ if (formatter.format(constant).getString().toLowerCase().equals(value)) return constant;
+ }
+
+ return option().pendingValue();
+ }
+
+ @Override
+ public boolean isValueValid(String value) {
+ value = value.toLowerCase();
+ for (String constant : getAllowedValues()) {
+ if (constant.equals(value)) return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected String getValidValue(String value, int offset) {
+ return getValidEnumConstants(value)
+ .skip(offset)
+ .findFirst()
+ .orElseGet(this::getString);
+ }
+
+ /**
+ * Filters and sorts through enum constants for those whose {@link #formatter} result equals {@code value}
+ *
+ * @return a sorted stream containing enum constants associated with the {@code value}
+ * @implNote The return value of {@link #formatter} on each enum constant should be unique in order to ensure accuracy
+ */
+ @NotNull
+ protected Stream<String> getValidEnumConstants(String value) {
+ String valueLowerCase = value.toLowerCase();
+ return getAllowedValues().stream()
+ .filter(constant -> constant.toLowerCase().contains(valueLowerCase))
+ .sorted((s1, s2) -> {
+ String s1LowerCase = s1.toLowerCase();
+ String s2LowerCase = s2.toLowerCase();
+ if (s1LowerCase.startsWith(valueLowerCase) && !s2LowerCase.startsWith(valueLowerCase)) return -1;
+ if (!s1LowerCase.startsWith(valueLowerCase) && s2LowerCase.startsWith(valueLowerCase)) return 1;
+ return s1.compareTo(s2);
+ });
+ }
+
+ @Override
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ return new EnumDropdownControllerElement<>(this, screen, widgetDimension);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/EnumDropdownControllerElement.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/EnumDropdownControllerElement.java
new file mode 100644
index 0000000..2df6f6b
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/EnumDropdownControllerElement.java
@@ -0,0 +1,25 @@
+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 EnumDropdownControllerElement<E extends Enum<E>> extends AbstractDropdownControllerElement<E, String> {
+ private final EnumDropdownController<E> controller;
+
+ public EnumDropdownControllerElement(EnumDropdownController<E> control, YACLScreen screen, Dimension<Integer> dim) {
+ super(control, screen, dim);
+ this.controller = control;
+ }
+
+ @Override
+ public List<String> computeMatchingValues() {
+ return controller.getValidEnumConstants(inputField).toList();
+ }
+
+ @Override
+ public String getString(String object) {
+ return object;
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemController.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemController.java
new file mode 100644
index 0000000..ac903c7
--- /dev/null
+++ b/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)
+ .findFirst()
+ .map(ResourceLocation::toString)
+ .orElseGet(this::getString);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ return new ItemControllerElement(this, screen, widgetDimension);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java
new file mode 100644
index 0000000..1617c41
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java
@@ -0,0 +1,87 @@
+package dev.isxander.yacl3.gui.controllers.dropdown;
+
+import dev.isxander.yacl3.api.utils.Dimension;
+import dev.isxander.yacl3.gui.YACLScreen;
+import dev.isxander.yacl3.gui.utils.ItemRegistryHelper;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class ItemControllerElement extends AbstractDropdownControllerElement<Item, ResourceLocation> {
+ private final ItemController itemController;
+ protected Item currentItem = null;
+ protected Map<ResourceLocation, Item> matchingItems = new HashMap<>();
+
+
+ public ItemControllerElement(ItemController control, YACLScreen screen, Dimension<Integer> dim) {
+ super(control, screen, dim);
+ this.itemController = control;
+ }
+
+ @Override
+ protected void drawValueText(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
+ var oldDimension = getDimension();
+ setDimension(getDimension().withWidth(getDimension().width() - getDecorationPadding()));
+ super.drawValueText(graphics, mouseX, mouseY, delta);
+ setDimension(oldDimension);
+ if (currentItem != null) {
+ graphics.renderFakeItem(new ItemStack(currentItem), getDimension().xLimit() - getXPadding() - getDecorationPadding() + 2, getDimension().y() + 2);
+ }
+ }
+
+ @Override
+ public List<ResourceLocation> computeMatchingValues() {
+ List<ResourceLocation> identifiers = ItemRegistryHelper.getMatchingItemIdentifiers(inputField).toList();
+ currentItem = ItemRegistryHelper.getItemFromName(inputField, null);
+ for (ResourceLocation identifier : identifiers) {
+ matchingItems.put(identifier, BuiltInRegistries.ITEM.get(identifier));
+ }
+ return identifiers;
+ }
+
+ @Override
+ protected void renderDropdownEntry(GuiGraphics graphics, ResourceLocation identifier, int n) {
+ super.renderDropdownEntry(graphics, identifier, n);
+ graphics.renderFakeItem(new ItemStack(matchingItems.get(identifier)), getDimension().xLimit() - getDecorationPadding() - 2, getDimension().y() + n * getDimension().height() + 4);
+ }
+
+ @Override
+ public String getString(ResourceLocation identifier) {
+ return identifier.toString();
+ }
+
+ @Override
+ protected int getDecorationPadding() {
+ return 16;
+ }
+
+ @Override
+ protected int getDropdownEntryPadding() {
+ return 4;
+ }
+
+ @Override
+ protected int getControlWidth() {
+ return super.getControlWidth() + getDecorationPadding();
+ }
+
+ @Override
+ protected Component getValueText() {
+ if (inputField.isEmpty() || itemController == null)
+ return super.getValueText();
+
+ if (inputFieldFocused)
+ return Component.literal(inputField);
+
+ return itemController.option().pendingValue().getDescription();
+ }
+}