From 2dc6d80b80edc7521532be9bda1eb301d5e6c100 Mon Sep 17 00:00:00 2001 From: 0xar-ds Date: Tue, 3 Mar 2026 08:05:28 -0300 Subject: [PATCH 1/7] Add Container Slot Renderer Signed-off-by: 0xar-ds --- .../config/util/api/SlotElement.java | 127 ++++++++++++++++++ .../config/util/api/SlotProvider.java | 8 ++ .../config/util/api/SlotProviders.java | 46 +++++++ .../util/api/annotations/ContainerSlot.java | 15 +++ .../config/util/client/SlotRenderer.java | 44 ++++++ .../util/client/SlotRendererProvider.java | 23 ++++ .../options/AbstractSelectorOverlay.java | 39 ++++++ .../components/options/ResetOptionWidget.java | 19 +++ .../options/SelectorOptionWidget.java | 17 +++ .../options/types/SlotNumberOptionWidget.java | 35 +++++ .../options/types/SlotOptionWidget.java | 103 ++++++++++++++ .../options/types/selector/ContainerCell.java | 63 +++++++++ 12 files changed, 539 insertions(+) create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotElement.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProvider.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProviders.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/api/annotations/ContainerSlot.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/SlotRenderer.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/SlotRendererProvider.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/AbstractSelectorOverlay.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/ResetOptionWidget.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/SelectorOptionWidget.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotNumberOptionWidget.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotOptionWidget.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ContainerCell.java diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotElement.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotElement.java new file mode 100644 index 00000000..253c8549 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotElement.java @@ -0,0 +1,127 @@ +package com.github.mkram17.bazaarutils.config.util.api; + +import com.github.mkram17.bazaarutils.config.util.api.annotations.ContainerSlot; +import com.teamresourceful.resourcefulconfig.api.annotations.Comment; +import com.teamresourceful.resourcefulconfig.api.annotations.ConfigEntry; +import com.teamresourceful.resourcefulconfig.api.types.ResourcefulConfigElement; +import com.teamresourceful.resourcefulconfig.api.types.elements.ResourcefulConfigEntryElement; +import com.teamresourceful.resourcefulconfig.api.types.entries.ResourcefulConfigEntry; +import com.teamresourceful.resourcefulconfig.api.types.entries.ResourcefulConfigValueEntry; +import com.teamresourceful.resourcefulconfig.api.types.options.EntryType; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.function.Predicate; + +public final class SlotElement implements ResourcefulConfigEntryElement { + private final ResourcefulConfigEntryElement delegate; + + private final int rows; + private final int cols; + + private final SlotProvider provider; + + private SlotElement(ResourcefulConfigEntryElement delegate, int rows, int cols, SlotProvider provider) { + this.delegate = delegate; + this.rows = rows; + this.cols = cols; + this.provider = provider; + } + + public static SlotElement wrap(ResourcefulConfigElement element) { + if (!(element instanceof ResourcefulConfigEntryElement entry)) return null; + if (!(entry.entry() instanceof ResourcefulConfigValueEntry value)) return null; + if (value.type() != EntryType.INTEGER) return null; + + Field field = entryField(entry); + + if (field == null || !field.isAnnotationPresent(ContainerSlot.class)) return null; + + ContainerSlot options = field.getAnnotation(ContainerSlot.class); + SlotProvider provider = SlotProviders.get(options.provider()); + + return new SlotElement(entry, options.rows(), options.cols(), provider); + } + + public int rows() { + return rows; + } + + public int cols() { + return cols; + } + + public int totalSlots() { + return rows * cols; + } + + public SlotProvider provider() { + return provider; + } + + public ResourcefulConfigValueEntry valueEntry() { + return (ResourcefulConfigValueEntry) delegate.entry(); + } + + public Text title() { + Field field = entryField(delegate); + + if (field != null) { + ConfigEntry options = field.getAnnotation(ConfigEntry.class); + + if (options != null && !options.translation().isEmpty()) return Text.translatable(options.translation()); + } + + return Text.literal(delegate.id()); + } + + public Text description() { + Field field = entryField(delegate); + + if (field != null) { + Comment comment = field.getAnnotation(Comment.class); + + if (comment != null) { + return !comment.translation().isEmpty() + ? Text.translatable(comment.translation()) + : Text.literal(comment.value()); + } + } + + return Text.empty(); + } + + @Override public ResourcefulConfigEntry entry() { + return delegate.entry(); + } + + @Override public String id() { + return delegate.id(); + } + + @Override public Identifier renderer() { + return delegate.renderer(); + } + + @Override public boolean isHidden() { + return delegate.isHidden(); + } + + @Override public boolean search(Predicate predicate) { + return delegate.search(predicate); + } + + private static Field entryField(ResourcefulConfigEntryElement delegate) { + try { + Method field = delegate.entry().getClass().getDeclaredMethod("field"); + + field.setAccessible(true); + + return (Field) field.invoke(delegate.entry()); + } catch (Exception ignored) { + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProvider.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProvider.java new file mode 100644 index 00000000..e93125e3 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProvider.java @@ -0,0 +1,8 @@ +package com.github.mkram17.bazaarutils.config.util.api; + +import net.minecraft.item.ItemStack; + +@FunctionalInterface +public interface SlotProvider { + ItemStack getStack(int slotIndex); +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProviders.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProviders.java new file mode 100644 index 00000000..7676001e --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProviders.java @@ -0,0 +1,46 @@ +package com.github.mkram17.bazaarutils.config.util.api; + +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.TooltipDisplayComponent; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; + +import java.util.HashMap; +import java.util.Map; + +public final class SlotProviders { + private static final Map REGISTRY = new HashMap<>(); + + private SlotProviders() {} + + public static void register(String key, SlotProvider provider) { + REGISTRY.put(key, provider); + } + + public static SlotProvider get(String key) { + if (key == null || key.isEmpty()) return slot -> ItemStack.EMPTY; + + return REGISTRY.getOrDefault(key, slot -> ItemStack.EMPTY); + } + + public static ItemStack named(Item item, Text name) { + return named(item, 1, name); + } + + public static ItemStack named(Item item, int count, Text name) { + ItemStack stack = new ItemStack(item, count); + stack.set(DataComponentTypes.CUSTOM_NAME, name); + + return stack; + } + + public static ItemStack hiddenTooltip(Item item, int count) { + ItemStack stack = new ItemStack(item, count); + + stack.set(DataComponentTypes.TOOLTIP_DISPLAY, new TooltipDisplayComponent(true, new ReferenceLinkedOpenHashSet<>())); + + return stack; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/annotations/ContainerSlot.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/annotations/ContainerSlot.java new file mode 100644 index 00000000..cd1c4abc --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/annotations/ContainerSlot.java @@ -0,0 +1,15 @@ +package com.github.mkram17.bazaarutils.config.util.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ContainerSlot { + int rows(); + int cols(); + + String provider() default ""; +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/SlotRenderer.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/SlotRenderer.java new file mode 100644 index 00000000..ce49720c --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/SlotRenderer.java @@ -0,0 +1,44 @@ +package com.github.mkram17.bazaarutils.config.util.client; + +import com.github.mkram17.bazaarutils.config.util.api.SlotElement; +import com.github.mkram17.bazaarutils.config.util.client.components.options.ResetOptionWidget; +import com.github.mkram17.bazaarutils.config.util.client.components.options.types.SlotNumberOptionWidget; +import com.github.mkram17.bazaarutils.config.util.client.components.options.types.SlotOptionWidget; +import com.teamresourceful.resourcefulconfig.api.client.ResourcefulConfigElementRenderer; +import com.teamresourceful.resourcefulconfig.api.types.entries.ResourcefulConfigValueEntry; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.text.Text; + +import java.util.List; + +public record SlotRenderer(SlotElement element) implements ResourcefulConfigElementRenderer { + @Override + public Text title() { + return element.title(); + } + + @Override + public Text description() { + return element.description(); + } + + @Override + public List widgets() { + ResourcefulConfigValueEntry entry = element.valueEntry(); + + SlotOptionWidget slotWidget = new SlotOptionWidget( + element, + entry::getInt, + entry::setInt + ); + + SlotNumberOptionWidget numberWidget = new SlotNumberOptionWidget(element); + + ClickableWidget resetWidget = ResetOptionWidget.of(() -> { + entry.reset(); + numberWidget.reset(); + }); + + return List.of(slotWidget, numberWidget, resetWidget); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/SlotRendererProvider.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/SlotRendererProvider.java new file mode 100644 index 00000000..6cc43268 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/SlotRendererProvider.java @@ -0,0 +1,23 @@ +package com.github.mkram17.bazaarutils.config.util.client; + +import com.github.mkram17.bazaarutils.config.util.api.SlotElement; +import com.teamresourceful.resourcefulconfig.api.client.ResourcefulConfigUI; +import net.minecraft.util.Identifier; + +public final class SlotRendererProvider { + private SlotRendererProvider() {} + + public static void register() { + ResourcefulConfigUI.registerElementRenderer( + Identifier.of("bazaarutils", "slot"), + element -> { + SlotElement se = SlotElement.wrap(element); + return se != null ? new SlotRenderer(se) : null; + } + ); + + registerElementProviders(); + } + + private static void registerElementProviders() {} +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/AbstractSelectorOverlay.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/AbstractSelectorOverlay.java new file mode 100644 index 00000000..bc1b8b72 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/AbstractSelectorOverlay.java @@ -0,0 +1,39 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options; + +import com.teamresourceful.resourcefulconfig.client.components.ModSprites; +import com.teamresourceful.resourcefulconfig.client.screens.base.OverlayScreen; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.Click; +import net.minecraft.client.gui.DrawContext; +import org.jetbrains.annotations.NotNull; + +public abstract class AbstractSelectorOverlay extends OverlayScreen { + protected int ox, oy, ow, oh; + + protected AbstractSelectorOverlay() { + super(MinecraftClient.getInstance().currentScreen); + } + + protected boolean isOverOverlay(double mouseX, double mouseY) { + return mouseX >= ox && mouseX <= ox + ow && mouseY >= oy && mouseY <= oy + oh; + } + + @Override + public void renderBackground(@NotNull DrawContext context, int mouseX, int mouseY, float delta) { + super.renderBackground(context, mouseX, mouseY, delta); + context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, ModSprites.ACCENT, ox, oy, ow, oh); + context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, ModSprites.BUTTON, ox + 1, oy + 1, ow - 2, oh - 2); + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + if (click.button() != 0 || isOverOverlay(click.x(), click.y())) { + return super.mouseClicked(click, doubled); + } + + close(); + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/ResetOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/ResetOptionWidget.java new file mode 100644 index 00000000..88758b3c --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/ResetOptionWidget.java @@ -0,0 +1,19 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options; + +import com.teamresourceful.resourcefulconfig.client.UIConstants; +import com.teamresourceful.resourcefulconfig.client.components.ModSprites; +import com.teamresourceful.resourcefulconfig.client.components.base.SpriteButton; +import net.minecraft.client.gui.widget.ClickableWidget; + +public final class ResetOptionWidget { + private ResetOptionWidget() {} + + public static ClickableWidget of(Runnable onPress) { + return SpriteButton.builder(12, 12) + .padding(2) + .sprite(ModSprites.RESET) + .tooltip(UIConstants.RESET) + .onPress(onPress) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/SelectorOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/SelectorOptionWidget.java new file mode 100644 index 00000000..4fc50c7a --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/SelectorOptionWidget.java @@ -0,0 +1,17 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options; + +import com.teamresourceful.resourcefulconfig.client.components.base.SpriteButton; +import com.teamresourceful.resourcefulconfig.client.components.options.types.ResetableWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public abstract class SelectorOptionWidget extends SpriteButton implements ResetableWidget { + protected static final Text SELECT = Text.translatable("bazaarutils.rconfig.ui.constant.select"); + + protected SelectorOptionWidget(Identifier sprite, Text tooltip) { + super(12, 12, 2, sprite, () -> {}, tooltip); + } + + @Override + public void reset() {} +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotNumberOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotNumberOptionWidget.java new file mode 100644 index 00000000..5ddcccd7 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotNumberOptionWidget.java @@ -0,0 +1,35 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options.types; + +import com.github.mkram17.bazaarutils.config.util.api.SlotElement; +import com.teamresourceful.resourcefulconfig.client.components.options.types.NumberOptionWidget; +import com.teamresourceful.resourcefulconfig.client.components.options.types.ResetableWidget; + +public class SlotNumberOptionWidget extends NumberOptionWidget implements ResetableWidget { + private final int maxSlot; + + public SlotNumberOptionWidget(SlotElement element) { + super( + element.valueEntry()::getInt, + value -> { + element.valueEntry().setInt(value); + return true; + }, + s -> { + int value = Integer.parseInt(s); + int max = element.totalSlots() - 1; + if (value < 0 || value > max) throw new NumberFormatException(); + return value; + }, + NumberOptionWidget.INTEGER_FILTER + ); + this.maxSlot = element.totalSlots() - 1; + } + + private static final boolean canExpand = false; + + @Override + public void updateIfFocused() { + // if (!canExpand) return; + // rather than to do an unless check, we just no-op, as it should never expand. + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotOptionWidget.java new file mode 100644 index 00000000..8cf65f83 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotOptionWidget.java @@ -0,0 +1,103 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options.types; + +import com.github.mkram17.bazaarutils.config.util.api.SlotElement; +import com.github.mkram17.bazaarutils.config.util.client.components.options.AbstractSelectorOverlay; +import com.github.mkram17.bazaarutils.config.util.client.components.options.SelectorOptionWidget; +import com.github.mkram17.bazaarutils.config.util.client.components.options.types.selector.ContainerCell; +import com.teamresourceful.resourcefulconfig.client.UIConstants; +import com.teamresourceful.resourcefulconfig.client.components.ModSprites; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class SlotOptionWidget extends SelectorOptionWidget { + private static final int SIZE = 12; + + private final SlotElement element; + private final Supplier getter; + private final Consumer setter; + + public SlotOptionWidget(SlotElement element, Supplier getter, Consumer setter) { + super(ModSprites.EDIT, UIConstants.EDIT); + this.element = element; + this.getter = getter; + this.setter = setter; + } + + @Override + public void onPress(@NotNull net.minecraft.client.input.AbstractInput modifiers) { + MinecraftClient.getInstance().setScreen(new SlotSelector(this, element, getter.get(), setter)); + } + + public static class SlotSelector extends AbstractSelectorOverlay { + private final SlotOptionWidget source; + + private static final int PADDING = 4; + private static final int SPACING = 2; + + private final SlotElement element; + + private final Consumer setter; + + private int selectedSlot; + + private final List cellWidgets = new ArrayList<>(); + + public SlotSelector(SlotOptionWidget source, SlotElement element, int currentSlot, Consumer setter) { + this.source = source; + this.element = element; + this.setter = setter; + this.selectedSlot = currentSlot; + } + + private void rebuildCells() { + cellWidgets.forEach(this::remove); + cellWidgets.clear(); + + int startX = ox + PADDING; + int startY = oy + PADDING; + + for (int slot = 0; slot < element.totalSlots(); slot++) { + final int s = slot; + int col = slot % element.cols(); + int row = slot / element.cols(); + ItemStack stack = element.provider().getStack(slot); + + ContainerCell cell = new ContainerCell( + startX + col * ContainerCell.CELL_SIZE, + startY + row * ContainerCell.CELL_SIZE, + stack, + slot == selectedSlot, + () -> { + setter.accept(s); + close(); + } + ); + cellWidgets.add(cell); + addDrawableChild(cell); + } + } + + @Override + protected void init() { + ow = PADDING * 2 + element.cols() * ContainerCell.CELL_SIZE; + oh = PADDING * 2 + element.rows() * ContainerCell.CELL_SIZE; + + oy = (source.getY() + source.getHeight() + SPACING + oh <= this.height) + ? source.getY() + source.getHeight() + SPACING + : source.getY() - oh - SPACING; + + int centerX = source.getX() + source.getWidth() / 2; + ox = MathHelper.clamp(centerX - ow / 2, 0, this.width - ow); + + rebuildCells(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ContainerCell.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ContainerCell.java new file mode 100644 index 00000000..a4a4ad83 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ContainerCell.java @@ -0,0 +1,63 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options.types.selector; + +import com.teamresourceful.resourcefulconfig.client.components.base.BaseWidget; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.Click; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.TooltipDisplayComponent; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; + +public class ContainerCell extends BaseWidget { + public static final int CELL_SIZE = 18; + private static final Identifier SLOT_SPRITE = Identifier.ofVanilla("container/slot"); + + private final ItemStack stack; + + private final boolean selected; + + private final Runnable onSelect; + + public ContainerCell(int x, int y, ItemStack stack, boolean selected, Runnable onSelect) { + super(CELL_SIZE, CELL_SIZE); + setPosition(x, y); + this.stack = stack; + this.selected = selected; + this.onSelect = onSelect; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, SLOT_SPRITE, getX(), getY(), CELL_SIZE, CELL_SIZE); + + if (selected) { + context.fill(getX() + 1, getY() + 1, getX() + CELL_SIZE - 1, getY() + CELL_SIZE - 1, 0x8800AA00); + } else if (isHovered()) { + context.fill(getX() + 1, getY() + 1, getX() + CELL_SIZE - 1, getY() + CELL_SIZE - 1, 0x80FFFFFF); + } + + if (!stack.isEmpty()) { + context.drawItem(stack, getX() + 1, getY() + 1); + + if (isHovered() && !stack.getOrDefault(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplayComponent.DEFAULT).hideTooltip()) { + context.drawTooltip( + MinecraftClient.getInstance().textRenderer, + stack.getTooltip(Item.TooltipContext.DEFAULT, null, TooltipType.BASIC), + mouseX, mouseY + ); + } + } + + this.applyCursor(context); + } + + @Override + public void onClick(@NotNull Click event, boolean doubled) { + onSelect.run(); + } +} \ No newline at end of file From 807442c322e0b8028bcdf6abf66e5c5c53e23e95 Mon Sep 17 00:00:00 2001 From: 0xar-ds Date: Tue, 3 Mar 2026 08:50:14 -0300 Subject: [PATCH 2/7] Add Item Renderer Signed-off-by: 0xar-ds --- .../config/util/api/ItemElement.java | 123 +++++++++ .../util/api/ResourcefulConfigItems.java | 41 +++ .../config/util/api/annotations/ItemTag.java | 12 + .../config/util/client/ItemRenderer.java | 57 +++++ .../util/client/ItemRendererProvider.java | 19 ++ .../options/types/ItemOptionWidget.java | 235 ++++++++++++++++++ .../options/types/ItemStringOptionWidget.java | 58 +++++ .../options/types/selector/ItemCell.java | 12 + 8 files changed, 557 insertions(+) create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/api/ItemElement.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/api/ResourcefulConfigItems.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/api/annotations/ItemTag.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRenderer.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRendererProvider.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemOptionWidget.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemStringOptionWidget.java create mode 100644 src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ItemCell.java diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ItemElement.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ItemElement.java new file mode 100644 index 00000000..bf9ffe03 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ItemElement.java @@ -0,0 +1,123 @@ +package com.github.mkram17.bazaarutils.config.util.api; + +import com.github.mkram17.bazaarutils.config.util.api.annotations.ItemTag; +import com.teamresourceful.resourcefulconfig.api.annotations.Comment; +import com.teamresourceful.resourcefulconfig.api.annotations.ConfigEntry; +import com.teamresourceful.resourcefulconfig.api.types.ResourcefulConfigElement; +import com.teamresourceful.resourcefulconfig.api.types.elements.ResourcefulConfigEntryElement; +import com.teamresourceful.resourcefulconfig.api.types.entries.ResourcefulConfigEntry; +import com.teamresourceful.resourcefulconfig.api.types.entries.ResourcefulConfigValueEntry; +import com.teamresourceful.resourcefulconfig.api.types.options.EntryType; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.function.Predicate; + +public final class ItemElement implements ResourcefulConfigEntryElement { + private final ResourcefulConfigEntryElement delegate; + + private final String tag; + + private ItemElement(ResourcefulConfigEntryElement delegate, String tag) { + this.delegate = delegate; + this.tag = tag; + } + + public static ItemElement wrap(ResourcefulConfigElement element) { + if (!(element instanceof ResourcefulConfigEntryElement entry)) return null; + if (!(entry.entry() instanceof ResourcefulConfigValueEntry value)) return null; + if (!(value.type() == EntryType.STRING)) return null; + + Field field = entryField(entry); + + if (field == null) return null; + + String tag = field.isAnnotationPresent(ItemTag.class) ? field.getAnnotation(ItemTag.class).value() : ""; + + return new ItemElement(entry, tag); + } + + private static Field entryField(ResourcefulConfigEntryElement delegate) { + try { + Method field = delegate.entry().getClass().getDeclaredMethod("field"); + + field.setAccessible(true); + + return (Field) field.invoke(delegate.entry()); + } catch (Exception ignored) { + return null; + } + } + + public String tag() { + return tag; + } + + public ResourcefulConfigValueEntry valueEntry() { + return (ResourcefulConfigValueEntry) delegate.entry(); + } + + public Text title() { + Field field = entryField(); + + if (field != null) { + ConfigEntry options = field.getAnnotation(ConfigEntry.class); + + if (options != null && !options.translation().isEmpty()) { + return Text.translatable(options.translation()); + } + } + + return Text.literal(delegate.id()); + } + + public Text description() { + Field field = entryField(); + + if (field != null) { + Comment comment = field.getAnnotation(Comment.class); + + if (comment != null) { + return !comment.translation().isEmpty() + ? Text.translatable(comment.translation()) + : Text.literal(comment.value()); + } + } + + return Text.empty(); + } + + @Override public ResourcefulConfigEntry entry() { + return delegate.entry(); + } + + @Override public String id() { + return delegate.id(); + } + + @Override public Identifier renderer() { + return delegate.renderer(); + } + + @Override public boolean isHidden() { + return delegate.isHidden(); + } + + @Override public boolean search(Predicate predicate) { + return delegate.search(predicate); + } + + private Field entryField() { + try { + Method field = delegate.entry().getClass().getDeclaredMethod("field"); + + field.setAccessible(true); + + return (Field) field.invoke(delegate.entry()); + } catch (Exception ignored) { + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ResourcefulConfigItems.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ResourcefulConfigItems.java new file mode 100644 index 00000000..53c9cf19 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ResourcefulConfigItems.java @@ -0,0 +1,41 @@ +package com.github.mkram17.bazaarutils.config.util.api; + +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; + +import java.util.List; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public final class ResourcefulConfigItems { + private static Supplier> source = () -> Registries.ITEM.stream().toList(); + + private static Predicate globalFilter = item -> true; + + private ResourcefulConfigItems() {} + + public static void setSource(Supplier> source) { + ResourcefulConfigItems.source = source; + } + + public static void addGlobalFilter(Predicate filter) { + Predicate existing = ResourcefulConfigItems.globalFilter; + ResourcefulConfigItems.globalFilter = item -> existing.test(item) && filter.test(item); + } + + public static List getItems(String tag) { + List base = source.get().stream().filter(globalFilter).toList(); + if (tag == null || tag.isEmpty()) return base; + Identifier tagId = Identifier.tryParse(tag); + if (tagId == null) return base; + TagKey tagKey = TagKey.of(RegistryKeys.ITEM, tagId); + return base.stream().filter(item -> item.getRegistryEntry().isIn(tagKey)).toList(); + } + + public static List getItems() { + return getItems(""); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/annotations/ItemTag.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/annotations/ItemTag.java new file mode 100644 index 00000000..a014d617 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/annotations/ItemTag.java @@ -0,0 +1,12 @@ +package com.github.mkram17.bazaarutils.config.util.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ItemTag { + String value(); +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRenderer.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRenderer.java new file mode 100644 index 00000000..ce0f68fb --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRenderer.java @@ -0,0 +1,57 @@ +package com.github.mkram17.bazaarutils.config.util.client; + +import com.github.mkram17.bazaarutils.config.util.api.ItemElement; +import com.github.mkram17.bazaarutils.config.util.api.ResourcefulConfigItems; +import com.github.mkram17.bazaarutils.config.util.client.components.options.types.ItemOptionWidget; +import com.github.mkram17.bazaarutils.config.util.client.components.options.types.ItemStringOptionWidget; +import com.github.mkram17.bazaarutils.config.util.client.components.options.ResetOptionWidget; +import com.teamresourceful.resourcefulconfig.api.client.ResourcefulConfigElementRenderer; +import com.teamresourceful.resourcefulconfig.api.types.entries.ResourcefulConfigValueEntry; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.util.List; + +public record ItemRenderer(ItemElement element) implements ResourcefulConfigElementRenderer { + @Override + public Text title() { + return element.title(); + } + + @Override + public Text description() { + return element.description(); + } + + @Override + public List widgets() { + ResourcefulConfigValueEntry entry = element.valueEntry(); + + List items = ResourcefulConfigItems.getItems(element.tag()); + + ItemOptionWidget itemWidget = new ItemOptionWidget(items, entry::getString, entry::setString); + + ItemStringOptionWidget stringWidget = new ItemStringOptionWidget( + entry::getString, + s -> { + Identifier id = Identifier.tryParse(s); + if (id == null) return false; + if (items.stream().noneMatch(item -> Registries.ITEM.getId(item).equals(id))) return false; + entry.setString(s); + return true; + } + ); + + return List.of( + itemWidget, + stringWidget, + ResetOptionWidget.of(() -> { + entry.reset(); + stringWidget.reset(); + }) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRendererProvider.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRendererProvider.java new file mode 100644 index 00000000..8576fdd3 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRendererProvider.java @@ -0,0 +1,19 @@ +package com.github.mkram17.bazaarutils.config.util.client; + +import com.github.mkram17.bazaarutils.config.util.api.ItemElement; +import com.teamresourceful.resourcefulconfig.api.client.ResourcefulConfigUI; +import net.minecraft.util.Identifier; + +public final class ItemRendererProvider { + private ItemRendererProvider() {} + + public static void register() { + ResourcefulConfigUI.registerElementRenderer( + Identifier.of("bazaarutils", "item"), + element -> { + ItemElement ie = ItemElement.wrap(element); + return ie != null ? new ItemRenderer(ie) : null; + } + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemOptionWidget.java new file mode 100644 index 00000000..37b4f8c5 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemOptionWidget.java @@ -0,0 +1,235 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options.types; + +import com.github.mkram17.bazaarutils.config.util.client.components.options.AbstractSelectorOverlay; +import com.github.mkram17.bazaarutils.config.util.client.components.options.SelectorOptionWidget; +import com.github.mkram17.bazaarutils.config.util.client.components.options.types.selector.ContainerCell; +import com.github.mkram17.bazaarutils.config.util.client.components.options.types.selector.ItemCell; +import com.teamresourceful.resourcefulconfig.client.components.ModSprites; +import com.teamresourceful.resourcefulconfig.client.components.options.text.TextBox; +import com.teamresourceful.resourcefulconfig.client.utils.ListenableState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.input.AbstractInput; +import net.minecraft.client.input.CharInput; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class ItemOptionWidget extends SelectorOptionWidget { + protected static final Text SEARCH = Text.translatable("bazaarutils.rconfig.ui.constant.search"); + + private final List items; + private final Supplier getter; + private final Consumer setter; + + public ItemOptionWidget(List items, Supplier getter, Consumer setter) { + super(ModSprites.BUTTON, SELECT); + this.items = items; + this.getter = getter; + this.setter = setter; + } + + private @Nullable Item resolveItem() { + Identifier id = Identifier.tryParse(getter.get()); + + if (id == null) return null; + + return items.stream() + .filter(item -> Registries.ITEM.getId(item).equals(id)) + .findFirst() + .orElse(null); + } + + @Override + protected void drawIcon(DrawContext context, int mouseX, int mouseY, float delta) { + super.drawIcon(context, mouseX, mouseY, delta); + + Item item = resolveItem(); + + if (item != null) { + context.drawItem(new ItemStack(item), getX(), getY()); + } + } + + @Override + public void onPress(@NotNull AbstractInput modifiers) { + MinecraftClient.getInstance().setScreen(new ItemSelector(this)); + } + + public static class ItemSelector extends AbstractSelectorOverlay { + private final ItemOptionWidget source; + + private static final int PADDING = 4; + private static final int SPACING = 2; + + private static final int GRID_COLS = 8; + private static final int MAX_ROWS = 5; + + private static final int SEARCH_HEIGHT = 14; + private static final int OVERLAY_WIDTH = GRID_COLS * ContainerCell.CELL_SIZE + PADDING * 2; + + private final Consumer setter; + + private final List allItems; + private List filteredItems; + + private int scrollOffset = 0; + + private final List cellWidgets = new ArrayList<>(); + + private TextBox searchBox; + + public ItemSelector(ItemOptionWidget source) { + this.source = source; + this.setter = source.setter; + this.allItems = source.items; + this.filteredItems = new ArrayList<>(allItems); + } + + private int totalRows() { + return (int) Math.ceil(filteredItems.size() / (double) GRID_COLS); + } + + private int maxScroll() { + return Math.max(0, totalRows() - MAX_ROWS); + } + + private int visibleRows() { + return Math.min(MAX_ROWS, totalRows()); + } + + private int overlayHeight() { + return PADDING * 2 + SEARCH_HEIGHT + SPACING + MAX_ROWS * ContainerCell.CELL_SIZE; + } + + private void rebuildCells() { + cellWidgets.forEach(this::remove); + cellWidgets.clear(); + + scrollOffset = MathHelper.clamp(scrollOffset, 0, maxScroll()); + oh = overlayHeight(); + + int startX = ox + PADDING; + int startY = oy + PADDING + SEARCH_HEIGHT + SPACING; + int startIndex = scrollOffset * GRID_COLS; + + for (int i = 0; i < MAX_ROWS * GRID_COLS; i++) { + int itemIndex = startIndex + i; + if (itemIndex >= filteredItems.size()) break; + + Item item = filteredItems.get(itemIndex); + int col = i % GRID_COLS; + int row = i / GRID_COLS; + + ItemCell cell = new ItemCell( + startX + col * ContainerCell.CELL_SIZE, + startY + row * ContainerCell.CELL_SIZE, + item, + selected -> { + this.setter.accept(Registries.ITEM.getId(selected).toString()); + close(); + } + ); + cellWidgets.add(cell); + addDrawableChild(cell); + } + } + + private void applySearch(String query) { + String q = query.toLowerCase().trim(); + scrollOffset = 0; + + filteredItems = allItems.stream().filter(item -> { + if (q.isEmpty()) return true; + + String name = item.getName(new ItemStack(item)).getString().toLowerCase(); + String key = Registries.ITEM.getId(item).toString().toLowerCase(); + + return name.contains(q) || key.contains(q); + }).toList(); + } + + @Override + protected void init() { + ow = OVERLAY_WIDTH; + oh = overlayHeight(); + + oy = (source.getY() + source.getHeight() + SPACING + oh <= this.height) + ? source.getY() + source.getHeight() + SPACING + : source.getY() - oh - SPACING; + + int centerX = source.getX() + source.getWidth() / 2; + ox = MathHelper.clamp(centerX - ow / 2, 0, this.width - ow); + + ListenableState searchState = ListenableState.of(""); + searchState.registerListener(q -> { + applySearch(q); + rebuildCells(); + }); + + this.searchBox = new TextBox(ow - PADDING * 2, SEARCH_HEIGHT, searchState) { + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, ModSprites.BUTTON, getX(), getY(), getWidth(), getHeight()); + super.renderWidget(context, mouseX, mouseY, delta); + this.applyCursor(context); + } + }; + this.searchBox.setPosition(ox + PADDING, oy + PADDING); + this.searchBox.setPlaceholder(SEARCH.getString(), 0xFF808080); + addDrawableChild(this.searchBox); + + rebuildCells(); + } + + @Override + public void renderBackground(@NotNull DrawContext context, int mouseX, int mouseY, float delta) { + super.renderBackground(context, mouseX, mouseY, delta); + + if (maxScroll() > 0) { + int trackTop = oy + PADDING + SEARCH_HEIGHT + SPACING; + int trackHeight = visibleRows() * ContainerCell.CELL_SIZE; + int thumbHeight = Math.max(6, trackHeight * MAX_ROWS / totalRows()); + int thumbTop = trackTop + (trackHeight - thumbHeight) * scrollOffset / Math.max(1, maxScroll()); + context.fill(ox + ow - 3, trackTop, ox + ow - 1, trackTop + trackHeight, 0x44FFFFFF); + context.fill(ox + ow - 3, thumbTop, ox + ow - 1, thumbTop + thumbHeight, 0xAAFFFFFF); + } + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (!isOverOverlay(mouseX, mouseY)) return false; + + int newOffset = MathHelper.clamp(scrollOffset - (int) Math.signum(scrollY), 0, maxScroll()); + + if (newOffset != scrollOffset) { + scrollOffset = newOffset; + rebuildCells(); + } + + return true; + } + + @Override + public boolean charTyped(CharInput input) { + if (searchBox != null && !searchBox.isFocused()) { + setInitialFocus(searchBox); + return searchBox.charTyped(input); + } + + return super.charTyped(input); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemStringOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemStringOptionWidget.java new file mode 100644 index 00000000..0e842b66 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemStringOptionWidget.java @@ -0,0 +1,58 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options.types; + +import com.github.mkram17.bazaarutils.config.util.api.ResourcefulConfigItems; +import com.teamresourceful.resourcefulconfig.client.components.options.types.ResetableWidget; +import com.teamresourceful.resourcefulconfig.client.components.options.types.StringOptionWidget; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.util.Identifier; + +import java.util.function.Function; +import java.util.function.Supplier; + +public class ItemStringOptionWidget extends StringOptionWidget implements ResetableWidget { + private final Supplier getter; + + public ItemStringOptionWidget(Supplier getter, Function setter) { + super(getter, setter, false); + this.getter = getter; + } + + @Override + public void updateIfFocused() { + if (!isFocused()) { + Identifier id = Identifier.tryParse(getter.get()); + + Item resolved = id != null + ? ResourcefulConfigItems.getItems().stream() + .filter(item -> Registries.ITEM.getId(item).equals(id)) + .findFirst() + .orElse(null) + : null; + + setValue(resolved != null ? resolved.getName(new ItemStack(resolved)).getString() : getter.get()); + setCursorPosition(0); + setHighlightPos(0); + setTextColor(0xFFE0E0E0); + } + } + + @Override + public void setFocused(boolean focused) { + boolean wasFocused = isFocused(); + + super.setFocused(focused); + + if (focused && !wasFocused) { + setValue(getter.get()); + setCursorPosition(getValue().length()); + setHighlightPos(getValue().length()); + } + } + + @Override + public void reset() { + setValue(getter.get()); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ItemCell.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ItemCell.java new file mode 100644 index 00000000..9474dac8 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ItemCell.java @@ -0,0 +1,12 @@ +package com.github.mkram17.bazaarutils.config.util.client.components.options.types.selector; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import java.util.function.Consumer; + +public class ItemCell extends ContainerCell { + public ItemCell(int x, int y, Item item, Consumer onSelect) { + super(x, y, new ItemStack(item), false, () -> onSelect.accept(item)); + } +} \ No newline at end of file From a0c0e5291b9ad6462b8752b572fa232d62d78b67 Mon Sep 17 00:00:00 2001 From: 0xar-ds Date: Wed, 4 Mar 2026 01:22:54 -0300 Subject: [PATCH 3/7] Add consumed translations Signed-off-by: 0xar-ds --- src/main/resources/assets/bazaarutils/lang/en_us.json5 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/resources/assets/bazaarutils/lang/en_us.json5 b/src/main/resources/assets/bazaarutils/lang/en_us.json5 index a8bd2131..1e464d11 100644 --- a/src/main/resources/assets/bazaarutils/lang/en_us.json5 +++ b/src/main/resources/assets/bazaarutils/lang/en_us.json5 @@ -68,6 +68,13 @@ "mod.bazaarutils": "Bazaar Utils", "key.category.minecraft.bazaarutils": "Bazaar Utils", + "bazaarutils.rconfig.ui.{}": { + "constant.{}": { + "search": "Search...", + "select": "Select" + } + }, + "bazaarutils.hypixel.account_upgrades.bazaar_flipper.{}": { "not_upgraded.label": "Not Upgraded", "first_tier.label": "Bazaar Flipper I", From 2a8cb333fd27aa796caaf69313e4896c5fd327c2 Mon Sep 17 00:00:00 2001 From: 0xar-ds Date: Tue, 3 Mar 2026 22:30:35 -0300 Subject: [PATCH 4/7] Hook renderers Signed-off-by: 0xar-ds --- .../github/mkram17/bazaarutils/config/util/ConfigUtil.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/ConfigUtil.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/ConfigUtil.java index 40457792..78832040 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/util/ConfigUtil.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/ConfigUtil.java @@ -3,6 +3,8 @@ import com.github.mkram17.bazaarutils.BazaarUtils; import com.github.mkram17.bazaarutils.config.BUConfig; import com.github.mkram17.bazaarutils.config.patcher.ConfigPatches; +import com.github.mkram17.bazaarutils.config.util.client.ItemRendererProvider; +import com.github.mkram17.bazaarutils.config.util.client.SlotRendererProvider; import com.github.mkram17.bazaarutils.utils.Util; import com.google.gson.JsonObject; import com.teamresourceful.resourcefulconfig.api.client.ResourcefulConfigScreen; @@ -62,6 +64,9 @@ public static ResourcefulConfig register(Configurator configurator) { "— expected VERSION = " + (PATCHES.size() + 1) ); } + + ItemRendererProvider.register(); + SlotRendererProvider.register(); configurator.register(BUConfig.class, event -> PATCHES.forEach((version, patch) -> From 594f9b079e30741e9cfa571ce385fd0d33c4dfb4 Mon Sep 17 00:00:00 2001 From: 0xar-ds Date: Thu, 5 Mar 2026 03:38:41 -0300 Subject: [PATCH 5/7] Cache configurable item id parsing Consuming features/modules can use ResourcefulConfigItems#resolve to not recompute the identifier parsing & registry lookup of values that are very much likely to have already been computed Signed-off-by: 0xar-ds --- .../util/api/ResourcefulConfigItems.java | 28 +++++++++++++++++-- .../config/util/client/ItemRenderer.java | 7 +++-- .../options/types/ItemOptionWidget.java | 14 ++-------- .../options/types/ItemStringOptionWidget.java | 9 +----- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ResourcefulConfigItems.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ResourcefulConfigItems.java index 53c9cf19..b992bd8f 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ResourcefulConfigItems.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/ResourcefulConfigItems.java @@ -5,27 +5,49 @@ import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.tag.TagKey; import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Predicate; import java.util.function.Supplier; public final class ResourcefulConfigItems { - private static Supplier> source = () -> Registries.ITEM.stream().toList(); + private static final Map RESOLVED_CACHE = new HashMap<>(); - private static Predicate globalFilter = item -> true; + public static @Nullable Item resolve(String rawId) { + if (rawId == null || rawId.isEmpty()) return null; + + return RESOLVED_CACHE.computeIfAbsent(rawId, id -> { + Identifier identifier = Identifier.tryParse(id); + + if (identifier == null) return null; + + return source.get().stream() + .filter(item -> Registries.ITEM.getId(item).equals(identifier)) + .findFirst() + .orElse(null); + }); + } - private ResourcefulConfigItems() {} + + private static Supplier> source = () -> Registries.ITEM.stream().toList(); public static void setSource(Supplier> source) { ResourcefulConfigItems.source = source; } + private static Predicate globalFilter = item -> true; + public static void addGlobalFilter(Predicate filter) { Predicate existing = ResourcefulConfigItems.globalFilter; ResourcefulConfigItems.globalFilter = item -> existing.test(item) && filter.test(item); } + + private ResourcefulConfigItems() {} + public static List getItems(String tag) { List base = source.get().stream().filter(globalFilter).toList(); if (tag == null || tag.isEmpty()) return base; diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRenderer.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRenderer.java index ce0f68fb..90d2c6d6 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRenderer.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/ItemRenderer.java @@ -37,9 +37,10 @@ public List widgets() { ItemStringOptionWidget stringWidget = new ItemStringOptionWidget( entry::getString, s -> { - Identifier id = Identifier.tryParse(s); - if (id == null) return false; - if (items.stream().noneMatch(item -> Registries.ITEM.getId(item).equals(id))) return false; + Item resolved = ResourcefulConfigItems.resolve(s); + + if (resolved == null || !items.contains(resolved)) return false; + entry.setString(s); return true; } diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemOptionWidget.java index 37b4f8c5..ffd751d2 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemOptionWidget.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemOptionWidget.java @@ -1,5 +1,6 @@ package com.github.mkram17.bazaarutils.config.util.client.components.options.types; +import com.github.mkram17.bazaarutils.config.util.api.ResourcefulConfigItems; import com.github.mkram17.bazaarutils.config.util.client.components.options.AbstractSelectorOverlay; import com.github.mkram17.bazaarutils.config.util.client.components.options.SelectorOptionWidget; import com.github.mkram17.bazaarutils.config.util.client.components.options.types.selector.ContainerCell; @@ -41,22 +42,11 @@ public ItemOptionWidget(List items, Supplier getter, Consumer Registries.ITEM.getId(item).equals(id)) - .findFirst() - .orElse(null); - } - @Override protected void drawIcon(DrawContext context, int mouseX, int mouseY, float delta) { super.drawIcon(context, mouseX, mouseY, delta); - Item item = resolveItem(); + Item item = ResourcefulConfigItems.resolve(getter.get()); if (item != null) { context.drawItem(new ItemStack(item), getX(), getY()); diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemStringOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemStringOptionWidget.java index 0e842b66..98ebba38 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemStringOptionWidget.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/ItemStringOptionWidget.java @@ -22,14 +22,7 @@ public ItemStringOptionWidget(Supplier getter, Function @Override public void updateIfFocused() { if (!isFocused()) { - Identifier id = Identifier.tryParse(getter.get()); - - Item resolved = id != null - ? ResourcefulConfigItems.getItems().stream() - .filter(item -> Registries.ITEM.getId(item).equals(id)) - .findFirst() - .orElse(null) - : null; + Item resolved = ResourcefulConfigItems.resolve(getter.get()); setValue(resolved != null ? resolved.getName(new ItemStack(resolved)).getString() : getter.get()); setCursorPosition(0); From 47eed5733ce913406930e3f189e140970c65f34b Mon Sep 17 00:00:00 2001 From: 0xar-ds Date: Fri, 6 Mar 2026 02:35:28 -0300 Subject: [PATCH 6/7] Add Container slot-locking functionality Register a custom component, CUSTOM_SLOT_SELECTOR_LOCKED_COMPONENT, which the custom renderer uses to branch off whether a cell can be picked/specified. Signed-off-by: 0xar-ds --- .../components/options/types/SlotNumberOptionWidget.java | 9 +++++++++ .../components/options/types/selector/ContainerCell.java | 2 ++ .../utils/minecraft/components/CustomDataComponents.java | 2 ++ 3 files changed, 13 insertions(+) diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotNumberOptionWidget.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotNumberOptionWidget.java index 5ddcccd7..37befe7c 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotNumberOptionWidget.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/SlotNumberOptionWidget.java @@ -1,8 +1,10 @@ package com.github.mkram17.bazaarutils.config.util.client.components.options.types; import com.github.mkram17.bazaarutils.config.util.api.SlotElement; +import com.github.mkram17.bazaarutils.utils.minecraft.components.CustomDataComponents; import com.teamresourceful.resourcefulconfig.client.components.options.types.NumberOptionWidget; import com.teamresourceful.resourcefulconfig.client.components.options.types.ResetableWidget; +import net.minecraft.item.ItemStack; public class SlotNumberOptionWidget extends NumberOptionWidget implements ResetableWidget { private final int maxSlot; @@ -18,6 +20,13 @@ public SlotNumberOptionWidget(SlotElement element) { int value = Integer.parseInt(s); int max = element.totalSlots() - 1; if (value < 0 || value > max) throw new NumberFormatException(); + ItemStack stack = element.provider().getStack(value); + // Known undeseriable/bug behavior: + // to throw NumberFormatException() will cause the input box to fallback to the last typed-in value, + // not the last saved/valid value. This is a implementation detail of NumberOptionWidget, + // and although we may override and fix it with a custom setChangedListener(...) call, + // it'd be better off if upstream fixes this behavior. + if (!stack.isEmpty() && stack.contains(CustomDataComponents.SLOT_SELECTOR_LOCKED)) throw new NumberFormatException(); return value; }, NumberOptionWidget.INTEGER_FILTER diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ContainerCell.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ContainerCell.java index a4a4ad83..c8eb0423 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ContainerCell.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/client/components/options/types/selector/ContainerCell.java @@ -1,5 +1,6 @@ package com.github.mkram17.bazaarutils.config.util.client.components.options.types.selector; +import com.github.mkram17.bazaarutils.utils.minecraft.components.CustomDataComponents; import com.teamresourceful.resourcefulconfig.client.components.base.BaseWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gl.RenderPipelines; @@ -28,6 +29,7 @@ public ContainerCell(int x, int y, ItemStack stack, boolean selected, Runnable o setPosition(x, y); this.stack = stack; this.selected = selected; + this.active = !(!stack.isEmpty() && stack.contains(CustomDataComponents.SLOT_SELECTOR_LOCKED)); this.onSelect = onSelect; } diff --git a/src/main/java/com/github/mkram17/bazaarutils/utils/minecraft/components/CustomDataComponents.java b/src/main/java/com/github/mkram17/bazaarutils/utils/minecraft/components/CustomDataComponents.java index 9b78dfbc..7ade92cb 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/utils/minecraft/components/CustomDataComponents.java +++ b/src/main/java/com/github/mkram17/bazaarutils/utils/minecraft/components/CustomDataComponents.java @@ -12,10 +12,12 @@ public final class CustomDataComponents { public static ComponentType CUSTOM_SIZE; public static ComponentType SHOW_PRICE_CHART; + public static ComponentType SLOT_SELECTOR_LOCKED; public CustomDataComponents() { CUSTOM_SIZE = register("custom_size", ComponentType.builder().codec(Codec.STRING).build()); SHOW_PRICE_CHART = register("has_price_chart", ComponentType.builder().codec(Codec.BOOL).build()); + SLOT_SELECTOR_LOCKED = register("slot_selector_locked", ComponentType.builder().codec(Codec.BOOL).build()); } private static ComponentType register(String id, ComponentType type) { From 0d7e21f0c6be86ec5a71c73b5953dc44e2320e76 Mon Sep 17 00:00:00 2001 From: 0xar-ds Date: Fri, 6 Mar 2026 02:35:47 -0300 Subject: [PATCH 7/7] Refactor ItemStack SlotProviders helpers API to Builder-like Signed-off-by: 0xar-ds --- .../config/util/api/SlotProviders.java | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProviders.java b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProviders.java index 7676001e..75356e7d 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProviders.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/util/api/SlotProviders.java @@ -1,6 +1,8 @@ package com.github.mkram17.bazaarutils.config.util.api; -import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import com.github.mkram17.bazaarutils.BazaarUtils; +import com.github.mkram17.bazaarutils.utils.minecraft.components.CustomDataComponents; +import it.unimi.dsi.fastutil.objects.ReferenceSortedSets; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.TooltipDisplayComponent; import net.minecraft.item.Item; @@ -25,22 +27,53 @@ public static SlotProvider get(String key) { return REGISTRY.getOrDefault(key, slot -> ItemStack.EMPTY); } - public static ItemStack named(Item item, Text name) { - return named(item, 1, name); + public static SlotStack stack(Item item) { + return new SlotStack(item, 1); } - public static ItemStack named(Item item, int count, Text name) { - ItemStack stack = new ItemStack(item, count); - stack.set(DataComponentTypes.CUSTOM_NAME, name); - - return stack; + public static SlotStack stack(Item item, int count) { + return new SlotStack(item, count); } - public static ItemStack hiddenTooltip(Item item, int count) { - ItemStack stack = new ItemStack(item, count); + public static final class SlotStack { + + private final ItemStack stack; + + private SlotStack(Item item, int count) { + this.stack = new ItemStack(item, count); + } + + public SlotStack named(Text name) { + stack.set(DataComponentTypes.CUSTOM_NAME, name); + return this; + } + + public SlotStack named(String name) { + return named(Text.literal(name)); + } + + public SlotStack locked() { + stack.set(CustomDataComponents.SLOT_SELECTOR_LOCKED, true); + return this; + } + + public SlotStack hideTooltip() { + stack.set(DataComponentTypes.TOOLTIP_DISPLAY, new TooltipDisplayComponent(true, ReferenceSortedSets.emptySet())); + return this; + } + +// we hide attributes by default +// public SlotStack hideAttributes() { +// TooltipDisplayComponent current = stack.getOrDefault(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplayComponent.DEFAULT); +// stack.set(DataComponentTypes.TOOLTIP_DISPLAY, current.with(DataComponentTypes.ATTRIBUTE_MODIFIERS, true)); +// return this; +// } - stack.set(DataComponentTypes.TOOLTIP_DISPLAY, new TooltipDisplayComponent(true, new ReferenceLinkedOpenHashSet<>())); + public ItemStack build() { + TooltipDisplayComponent current = stack.getOrDefault(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplayComponent.DEFAULT); + stack.set(DataComponentTypes.TOOLTIP_DISPLAY, current.with(DataComponentTypes.ATTRIBUTE_MODIFIERS, true)); - return stack; + return stack; + } } } \ No newline at end of file