From ffe369f89766cedc8283bad8120480bc9691e303 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:39:49 -0400 Subject: [PATCH 01/84] fully implemented JeiRecipeViewerSlot --- .../integration/jei/JeiRecipeViewerSlot.java | 32 +++++++++++++------ .../jei/recipe/ModularUIRecipeCategory.java | 14 ++++---- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index cfe29f95..91b02c30 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -1,31 +1,43 @@ package brachy.modularui.integration.jei; +import brachy.modularui.api.drawable.IDrawable; +import brachy.modularui.api.widget.IWidget; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.entry.EntryList; +import brachy.modularui.widget.Widget; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import mezz.jei.api.gui.builder.IRecipeSlotBuilder; +import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Experimental public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget { + @Accessors(fluent = true) + @Getter + @Setter + private RecipeSlotRole recipeSlotRole; + @Getter + private EntryList value; + @Accessors(fluent = true) + @Getter + @Setter + private float chance = 1f; public JeiRecipeViewerSlot() { - throw new NotImplementedException(); - } + recipeSlotRole = RecipeSlotRole.RENDER_ONLY; - @Override - public JeiRecipeViewerSlot recipeSlotRole(RecipeSlotRole recipeSlotRole) { - return getThis(); + size(18, 18); } @Override public JeiRecipeViewerSlot value(EntryList entryList) { - return getThis(); - } - - @Override - public JeiRecipeViewerSlot chance(float chance) { + this.value = entryList; return getThis(); } } diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 4a02785b..d6254610 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -1,8 +1,10 @@ package brachy.modularui.integration.jei.recipe; import brachy.modularui.api.widget.IWidget; +import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.entry.EntryList; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.recipeviewer.util.RecipeScreenRenderingUtil; import brachy.modularui.screen.ModularPanel; @@ -68,10 +70,10 @@ private ModularScreen getModularScreen(T recipe) { return this.modularScreenCache.getUnchecked(recipe); } - private static void addJEISlot(IRecipeLayoutBuilder builder, IngredientProvider widget, + private static void addJEISlot(IRecipeLayoutBuilder builder, EntryList entries, JeiRecipeViewerSlot widget, RecipeIngredientRole role, int index) { var type = ModularUIJeiPlugin.getRuntime().getIngredientManager() - .getIngredientTypeChecked(widget.ingredientClass()); + .getIngredientTypeChecked(entries.getType()); if (type.isEmpty()) { return; } @@ -79,7 +81,7 @@ private static void addJEISlot(IRecipeLayoutBuilder builder, IngredientProvi Area widgetArea = widget.getArea(); IRecipeSlotBuilder slotBuilder = builder.addSlot(role, widgetArea.x, widgetArea.y); - slotBuilder.addIngredients(type.get(), widget.getIngredients().getStacks()); + slotBuilder.addIngredients(type.get(), entries.getStacks()); slotBuilder.setCustomRenderer(type.get(), new IIngredientRenderer<>() { @Override @@ -111,11 +113,11 @@ public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuse MutableInt i = new MutableInt(0); WidgetTree.foreachChildBFS(screen.getMainPanel(), widget -> { - if (!(widget instanceof IngredientProvider provider)) { + if (!(widget instanceof JeiRecipeViewerSlot provider)) { return true; } - RecipeIngredientRole role = mapToRole(provider.getRecipeRole()); - addJEISlot(builder, provider, role, i.getAndIncrement()); + RecipeIngredientRole role = mapToRole(provider.recipeSlotRole()); + addJEISlot(builder, provider.getValue(), provider, role, i.getAndIncrement()); return true; }, true); } From f532a939aa43bc609e141477054253e559f9f8b3 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:42:46 -0400 Subject: [PATCH 02/84] fixed background in JeiRecipeViewerSlot --- .../modularui/integration/jei/JeiRecipeViewerSlot.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 91b02c30..bf3e905e 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -2,6 +2,7 @@ import brachy.modularui.api.drawable.IDrawable; import brachy.modularui.api.widget.IWidget; +import brachy.modularui.drawable.GuiTextures; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.entry.EntryList; @@ -13,6 +14,9 @@ import lombok.experimental.Accessors; import mezz.jei.api.gui.builder.IRecipeSlotBuilder; import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; + +import net.minecraftforge.fluids.FluidStack; + import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.ApiStatus; @@ -38,6 +42,11 @@ public JeiRecipeViewerSlot() { @Override public JeiRecipeViewerSlot value(EntryList entryList) { this.value = entryList; + if (this.value.getType() == FluidStack.class) { + background(GuiTextures.SLOT_FLUID); + } else { + background(GuiTextures.SLOT_ITEM); // TODO other types + } return getThis(); } } From df3d87846c7283008ff548a5c5e3a56566c2e702 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Sun, 26 Jul 2026 22:04:48 -0400 Subject: [PATCH 03/84] implemented ReiRecipeViewerSlot --- .../integration/rei/ReiRecipeViewerSlot.java | 81 ++++++++++++++++++- .../rei/recipe/ModularUIREIDisplay.java | 43 ++++------ 2 files changed, 93 insertions(+), 31 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index f23a6717..cdc760a1 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -1,31 +1,104 @@ package brachy.modularui.integration.rei; +import brachy.modularui.drawable.GuiTextures; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.entry.EntryList; +import brachy.modularui.screen.viewport.ModularGuiContext; +import brachy.modularui.theme.WidgetThemeEntry; -import org.apache.commons.lang3.NotImplementedException; +import me.shedaniel.math.Point; +import me.shedaniel.rei.api.client.gui.widgets.Slot; + +import me.shedaniel.rei.api.client.gui.widgets.Widgets; + +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; import org.jetbrains.annotations.ApiStatus; +import java.util.function.UnaryOperator; + @ApiStatus.Experimental public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget { + @ApiStatus.Internal + @Getter + private Slot slotWidget; + private int x, y; + + @Accessors(fluent = true) + @Getter + private RecipeSlotRole recipeSlotRole; + @Getter + private EntryList value; + @Accessors(fluent = true) + @Getter + @Setter + private float chance = 1f; + public ReiRecipeViewerSlot() { - throw new NotImplementedException(); + super(); + slotWidget = Widgets.createSlot(new Point()).disableBackground(); + recipeSlotRole = RecipeSlotRole.RENDER_ONLY; + + size(18, 18); } @Override public ReiRecipeViewerSlot recipeSlotRole(RecipeSlotRole recipeSlotRole) { + this.recipeSlotRole = recipeSlotRole; + rebuildReiSlot(); return getThis(); } @Override public ReiRecipeViewerSlot value(EntryList entryList) { + this.value = entryList; + rebuildReiSlot(); + if (this.value.getType() == FluidStack.class) { + background(GuiTextures.SLOT_FLUID); + } else { + background(GuiTextures.SLOT_ITEM); // TODO other types + } return getThis(); } + @SuppressWarnings("unchecked") + private void rebuildReiSlot() { + slotWidget = Widgets.createSlot(new Point()).disableBackground(); + if (this.value.getType() == ItemStack.class) { + slotWidget.entries(REIStackConverter.ITEM.convertTo((EntryList) this.value, chance, UnaryOperator.identity())); + } else if (this.value.getType() == FluidStack.class) { + slotWidget.entries(REIStackConverter.FLUID.convertTo((EntryList) this.value, chance, UnaryOperator.identity())); + } + if (recipeSlotRole == RecipeSlotRole.INPUT) { + slotWidget.markInput(); + } else if (recipeSlotRole == RecipeSlotRole.OUTPUT) { + slotWidget.markOutput(); + } else { + slotWidget.unmarkInputOrOutput(); + } + } + @Override - public ReiRecipeViewerSlot chance(float chance) { - return getThis(); + public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + context.getGraphics().pose().translate(-this.x, -this.y, 0); + this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); + context.getGraphics().pose().translate(this.x, this.y, 0); + } + + @Override + public Result onMousePressed(int button) { + this.slotWidget.mouseClicked(getContext().getMouseX(), getContext().getAbsMouseY(), button); + return Result.SUCCESS; + } + + @Override + public Result onKeyPressed(int keyCode, int scanCode, int modifiers) { + return this.slotWidget.keyPressed(keyCode, scanCode, modifiers) ? Result.SUCCESS : Result.ACCEPT; } } diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index e1c43a59..a93dfc9e 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -9,6 +9,7 @@ import brachy.modularui.integration.recipeviewer.handlers.fluid.EmptyFluidTank; import brachy.modularui.integration.recipeviewer.util.RecipeScreenRenderingUtil; import brachy.modularui.integration.rei.REIStackConverter; +import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; import brachy.modularui.utils.memoization.MemoizedSupplier; @@ -18,6 +19,8 @@ import brachy.modularui.widgets.slot.FluidSlot; import brachy.modularui.widgets.slot.ItemSlot; +import me.shedaniel.rei.api.client.gui.widgets.Slot; + import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.network.chat.Component; @@ -72,16 +75,16 @@ public ModularUIREIDisplay(ResourceLocation recipeId, Supplier widgetSu }, Duration.ofSeconds(10)); WidgetTree.foreachChildBFS(widgetSupplier.get(), widget -> { - if (!(widget instanceof IngredientProvider provider)) return true; + if (!(widget instanceof ReiRecipeViewerSlot provider)) return true; - RecipeSlotRole role = provider.getRecipeRole(); + RecipeSlotRole role = provider.recipeSlotRole(); if (role == RecipeSlotRole.RENDER_ONLY) return true; - REIStackConverter.Converter converter = REIStackConverter.getForNullable(provider.ingredientClass()); + REIStackConverter.Converter converter = REIStackConverter.getForNullable(provider.getValue().getType()); if (converter == null) return true; @SuppressWarnings({"rawtypes", "unchecked"}) - EntryIngredient ingredient = ((REIStackConverter.Converter) converter).convertTo(provider); + EntryIngredient ingredient = EntryIngredient.of(provider.getSlotWidget().getEntries()); switch (role) { case INPUT -> inputEntries.add(ingredient); @@ -102,37 +105,22 @@ public List createWidgets(Rectangle bounds) { widgets.add(new UIWrapperWidget()); WidgetTree.foreachChildBFS(this.screen.get().getMainPanel(), widget -> { - if (!(widget instanceof IngredientProvider provider)) return true; + if (!(widget instanceof ReiRecipeViewerSlot provider)) return true; - RecipeSlotRole role = provider.getRecipeRole(); + RecipeSlotRole role = provider.recipeSlotRole(); if (role == RecipeSlotRole.RENDER_ONLY) return true; - REIStackConverter.Converter converter = REIStackConverter.getForNullable(provider.ingredientClass()); + REIStackConverter.Converter converter = REIStackConverter.getForNullable(provider.getValue().getType()); if (converter == null) return true; @SuppressWarnings({"rawtypes", "unchecked"}) - EntryIngredient ingredient = ((REIStackConverter.Converter) converter).convertTo(provider); + EntryIngredient ingredient = EntryIngredient.of(provider.getSlotWidget().getEntries()); Area area = widget.getArea(); - EntryWidget entryWidget = new EntryWidget(new Rectangle(area.x(), area.y(), area.w(), area.h())); - // Clear the MUI slots and add EMI slots based on them. - if (provider instanceof ItemSlot itemSlot) { - itemSlot.slot(RecipeScreenRenderingUtil.EMPTY_ITEM_HANDLER, 0).invisible(); - } else if (provider instanceof FluidSlot fluidSlot) { - fluidSlot.syncHandler(EmptyFluidTank.INSTANCE).invisible(); - } - - entryWidget.background(false) - .entries(ingredient.castAsList()); - if (role == RecipeSlotRole.INPUT) { - entryWidget.markIsInput(); - } else if (role == RecipeSlotRole.OUTPUT) { - entryWidget.markIsOutput(); - } else { - entryWidget.unmarkInputOrOutput(); - } - if (widget instanceof ITooltip tooltip && tooltip.hasTooltip()) { - if (tooltip.tooltip().getRichText() instanceof RichText richText) { + Slot entryWidget = provider.getSlotWidget(); +/* + if (provider.hasTooltip()) { + if (provider.tooltip().getRichText() instanceof RichText richText) { var textList = richText.getAsText(); entryWidget.tooltipProcessor(text -> { textList.forEach(line -> line.map(t -> text.add((Component) t), text::add)); @@ -140,6 +128,7 @@ public List createWidgets(Rectangle bounds) { }); } } +*/ widgets.add(entryWidget); return true; }, true); From e2d2526c3f93099d3241b5284c19b1b377a9ed64 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:12:31 -0400 Subject: [PATCH 04/84] did some requested changes --- .../integration/jei/JeiRecipeViewerSlot.java | 14 ++-------- .../integration/rei/REIStackConverter.java | 1 + .../integration/rei/ReiRecipeViewerSlot.java | 28 ++++++++++--------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index bf3e905e..c1e94f22 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -1,23 +1,15 @@ package brachy.modularui.integration.jei; -import brachy.modularui.api.drawable.IDrawable; -import brachy.modularui.api.widget.IWidget; import brachy.modularui.drawable.GuiTextures; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.entry.EntryList; -import brachy.modularui.widget.Widget; +import net.minecraftforge.fluids.FluidStack; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -import mezz.jei.api.gui.builder.IRecipeSlotBuilder; -import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; - -import net.minecraftforge.fluids.FluidStack; - -import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Experimental @@ -25,7 +17,7 @@ public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget value; @Accessors(fluent = true) @@ -34,8 +26,6 @@ public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget Converter getForNullable(Class clazz) { return (Converter) CONVERTERS.get(clazz); } + public static Optional> getFor(Class clazz) { return Optional.ofNullable(getForNullable(clazz)); } diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index cdc760a1..59bd3f01 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -7,17 +7,15 @@ import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.theme.WidgetThemeEntry; -import me.shedaniel.math.Point; -import me.shedaniel.rei.api.client.gui.widgets.Slot; - -import me.shedaniel.rei.api.client.gui.widgets.Widgets; - -import net.minecraft.world.item.ItemStack; import net.minecraftforge.fluids.FluidStack; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; +import me.shedaniel.math.Point; +import me.shedaniel.rei.api.client.gui.widgets.Slot; +import me.shedaniel.rei.api.client.gui.widgets.Widgets; +import me.shedaniel.rei.api.common.entry.EntryIngredient; import org.jetbrains.annotations.ApiStatus; import java.util.function.UnaryOperator; @@ -70,12 +68,8 @@ public ReiRecipeViewerSlot value(EntryList entryList) { @SuppressWarnings("unchecked") private void rebuildReiSlot() { slotWidget = Widgets.createSlot(new Point()).disableBackground(); - if (this.value.getType() == ItemStack.class) { - slotWidget.entries(REIStackConverter.ITEM.convertTo((EntryList) this.value, chance, UnaryOperator.identity())); - } else if (this.value.getType() == FluidStack.class) { - slotWidget.entries(REIStackConverter.FLUID.convertTo((EntryList) this.value, chance, UnaryOperator.identity())); - } - if (recipeSlotRole == RecipeSlotRole.INPUT) { + slotWidget.entries(convertToReiEntry(this.value, chance)); + if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { slotWidget.markInput(); } else if (recipeSlotRole == RecipeSlotRole.OUTPUT) { slotWidget.markOutput(); @@ -83,12 +77,20 @@ private void rebuildReiSlot() { slotWidget.unmarkInputOrOutput(); } } + private static EntryIngredient convertToReiEntry(EntryList entries, float chance) { + REIStackConverter.Converter converter = REIStackConverter.getForNullable(entries.getType()); + if (converter != null){ + return converter.convertTo(entries, chance, UnaryOperator.identity()); + } + return EntryIngredient.empty(); + } @Override public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + context.getGraphics().pose().pushPose(); context.getGraphics().pose().translate(-this.x, -this.y, 0); this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); - context.getGraphics().pose().translate(this.x, this.y, 0); + context.getGraphics().pose().popPose(); } @Override From e4139966e28307d134f92cbb4952e67f31c5648b Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 1 Aug 2026 16:30:50 +0300 Subject: [PATCH 05/84] Make TooltipLines store actual Components instead of FormattedText so REI is happier with our tooltips --- .../brachy/modularui/screen/RichTooltip.java | 9 ++++++++- .../brachy/modularui/utils/TooltipLines.java | 16 ++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/brachy/modularui/screen/RichTooltip.java b/src/main/java/brachy/modularui/screen/RichTooltip.java index 626534ab..72522f1c 100644 --- a/src/main/java/brachy/modularui/screen/RichTooltip.java +++ b/src/main/java/brachy/modularui/screen/RichTooltip.java @@ -23,12 +23,15 @@ import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; import net.minecraft.util.Mth; import net.minecraft.util.StringRepresentable; import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -162,8 +165,12 @@ public void draw(GuiContext context, ItemStack stack) { // vanilla event to gather additional tooltip TooltipLines textLines = copy.getAsText(); + // convert the List> to the type vanilla wants. Technically it's already valid, + // but some mods (looking at you REI) want Components that they do nothing with except downcast them into FormattedText anyway. + @SuppressWarnings("unchecked") + List> tooltipElements = (List>) (List) textLines; // noinspection UnstableApiUsage - var vanillaGatherEvent = new RenderTooltipEvent.GatherComponents(stack, screen.width, screen.height, textLines, this.maxWidth); + var vanillaGatherEvent = new RenderTooltipEvent.GatherComponents(stack, screen.width, screen.height, tooltipElements, this.maxWidth); if (MinecraftForge.EVENT_BUS.post(vanillaGatherEvent)) return; this.maxWidth = vanillaGatherEvent.getMaxWidth(); diff --git a/src/main/java/brachy/modularui/utils/TooltipLines.java b/src/main/java/brachy/modularui/utils/TooltipLines.java index 27a9fca0..c5e64e93 100755 --- a/src/main/java/brachy/modularui/utils/TooltipLines.java +++ b/src/main/java/brachy/modularui/utils/TooltipLines.java @@ -20,7 +20,7 @@ /** * A list that lazily parses a list of text-like and drawable types into a vanilla compatible types. */ -public class TooltipLines extends AbstractList> { +public class TooltipLines extends AbstractList> { private final List elements; private final List lines = new ArrayList<>(8); @@ -96,7 +96,7 @@ private Line parseNext() { } @Override - public Either get(int index) { + public Either get(int index) { buildUntil(index); return lines.get(index).text; } @@ -108,7 +108,7 @@ public int size() { } @Override - public Either remove(int index) { + public Either remove(int index) { buildUntil(index); Line line = lines.remove(index); @@ -126,7 +126,7 @@ public Either remove(int index) { } @Override - public void add(int index, Either s) { + public void add(int index, Either s) { buildUntil(index); int elementIndex = index >= this.lines.size() ? this.lastElementIndex : this.lines.get(index).index; lines.add(index, new Line(s, elementIndex, 1)); @@ -152,7 +152,7 @@ public void add(Component s) { } @Override - public Either set(int index, Either element) { + public Either set(int index, Either element) { Line line = lines.get(index); element.ifLeft(ft -> { if (!(ft instanceof Component)) { @@ -205,17 +205,17 @@ private static Component collapse(List components) { private static class Line { - private final Either text; + private final Either text; private final int length; private int index; - private Line(Either text, int index, int length) { + private Line(Either text, int index, int length) { this.text = text; this.index = index; this.length = length; } - private Line(FormattedText text, int index, int length) { + private Line(Component text, int index, int length) { this(Either.left(text), index, length); } From c1ff914c0ec3963c9e884f38659851461080b65e Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:21:07 +0300 Subject: [PATCH 06/84] Make REI/JEI recipe viewer compat act even more like EMI's --- .../integration/jei/JeiRecipeViewerSlot.java | 106 ++++++- .../integration/jei/ModularUIJeiPlugin.java | 10 + .../jei/recipe/ModularUIRecipeCategory.java | 237 +++++++++------- .../integration/jei/recipe/package-info.java | 7 +- .../util/RecipeScreenRenderingUtil.java | 75 ----- .../integration/rei/REIStackConverter.java | 8 + .../integration/rei/ReiRecipeViewerSlot.java | 10 +- .../rei/recipe/ModularUIREIDisplay.java | 262 ++++++++++-------- .../recipe/ModularUIREIDisplayCategory.java | 33 ++- 9 files changed, 434 insertions(+), 314 deletions(-) delete mode 100644 src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeScreenRenderingUtil.java diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index c1e94f22..118d4ff1 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -5,33 +5,61 @@ import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.entry.EntryList; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.navigation.ScreenPosition; import net.minecraftforge.fluids.FluidStack; +import brachy.modularui.screen.viewport.ModularGuiContext; +import brachy.modularui.theme.WidgetThemeEntry; + import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; +import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; +import mezz.jei.api.gui.inputs.RecipeSlotUnderMouse; +import mezz.jei.api.gui.widgets.ISlottedRecipeWidget; +import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.runtime.IIngredientManager; +import mezz.jei.library.gui.ingredients.ICycler; +import mezz.jei.library.gui.recipes.layout.builder.RecipeSlotBuilder; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.UnknownNullability; + +import java.util.Optional; @ApiStatus.Experimental -public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget { +public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget implements ISlottedRecipeWidget { + + @ApiStatus.Internal + @Getter @Setter private @UnknownNullability IRecipeSlotDrawable slotWidget; + @ApiStatus.Internal + @Setter private IFocusGroup focuses; + @ApiStatus.Internal + @Setter private ICycler cycler; + @Accessors(fluent = true) - @Getter - @Setter - private RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; - @Getter - private EntryList value; + @Getter private RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; + @Getter private EntryList value; @Accessors(fluent = true) - @Getter - @Setter - private float chance = 1f; + @Getter @Setter private float chance = 1f; public JeiRecipeViewerSlot() { + super(); + size(18, 18); } + @Override + public JeiRecipeViewerSlot recipeSlotRole(RecipeSlotRole recipeSlotRole) { + this.recipeSlotRole = recipeSlotRole; + rebuildJeiSlot(); + return getThis(); + } + @Override public JeiRecipeViewerSlot value(EntryList entryList) { this.value = entryList; + rebuildJeiSlot(); if (this.value.getType() == FluidStack.class) { background(GuiTextures.SLOT_FLUID); } else { @@ -39,4 +67,64 @@ public JeiRecipeViewerSlot value(EntryList entryList) { } return getThis(); } + + // TODO make sure this doesn't break everything (for example: search). It shouldn't but I'm not 100% on that. + private void rebuildJeiSlot() { + IIngredientManager ingredientManager = ModularUIJeiPlugin.getRuntime().getIngredientManager(); + + // kinda meh solution. this isn't API, but I can't find a good way to do this within the API. + RecipeSlotBuilder builder = new RecipeSlotBuilder(ingredientManager, 0, ModularUIJeiPlugin.mapToJeiRole(this.recipeSlotRole)); + JeiRecipeViewerSlot.addTypedIngredients(this.value, builder); + builder.setPosition(this.getArea().x, this.getArea().y); + + if (this.value.getType() == FluidStack.class) { + // special case fluid slots (this is why we can't have nice things.) + builder.setFluidRenderer(1, false, 18, 18); + } + + slotWidget = builder.build(this.focuses, this.cycler).second(); + } + + private static void addTypedIngredients(EntryList entries, RecipeSlotBuilder builder) { + IIngredientManager ingredientManager = ModularUIJeiPlugin.getRuntime().getIngredientManager(); + var ingredientType = ingredientManager.getIngredientTypeChecked(entries.getType()); + if (ingredientType.isEmpty()) { + return; + } + builder.addIngredients(ingredientType.get(), entries.getStacks()); + } + + @Override + public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + context.graphicsPose().pushPose(); + context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); + this.slotWidget.draw(context.getGraphics()); + context.graphicsPose().popPose(); + } +/* + @Override + public Result onMousePressed(int button) { + return this.slotWidget.mouseClicked(getContext().getMouseX(), getContext().getMouseY(), button) ? Result.SUCCESS : Result.ACCEPT; + } + + @Override + public Result onKeyPressed(int keyCode, int scanCode, int modifiers) { + return this.slotWidget.keyPressed(keyCode, scanCode, modifiers) ? Result.SUCCESS : Result.ACCEPT; + } +*/ + @Override + public Optional getSlotUnderMouse(double mouseX, double mouseY) { + if (isHovering()) { + return Optional.of(new RecipeSlotUnderMouse(this.slotWidget, this.getPosition())); + } + return Optional.empty(); + } + + @Override + public ScreenPosition getPosition() { + return new ScreenPosition(getArea().x, getArea().y); + } + + @Override + public void drawWidget(GuiGraphics guiGraphics, double mouseX, double mouseY) {} } diff --git a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java index 275950c7..aeaa9a06 100644 --- a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java +++ b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java @@ -2,6 +2,7 @@ import brachy.modularui.ModularUI; import brachy.modularui.integration.jei.handler.JeiScreenHandler; +import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.screen.ContainerScreenWrapper; import brachy.modularui.screen.ScreenWrapper; @@ -12,6 +13,7 @@ import mezz.jei.api.IModPlugin; import mezz.jei.api.JeiPlugin; import mezz.jei.api.helpers.IJeiHelpers; +import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.registration.IGuiHandlerRegistration; import mezz.jei.api.registration.IRecipeCategoryRegistration; import mezz.jei.api.registration.IRecipeTransferRegistration; @@ -62,4 +64,12 @@ public void registerRecipeTransferHandlers(IRecipeTransferRegistration registrat //JeiContainerHandler.register(ModularContainerMenu.class, registration); } + public static RecipeIngredientRole mapToJeiRole(RecipeSlotRole slotRole) { + return switch (slotRole) { + case INPUT -> RecipeIngredientRole.INPUT; + case OUTPUT -> RecipeIngredientRole.OUTPUT; + case CATALYST -> RecipeIngredientRole.CATALYST; + case RENDER_ONLY -> RecipeIngredientRole.RENDER_ONLY; + }; + } } diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index d6254610..e3a52e18 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -1,67 +1,65 @@ package brachy.modularui.integration.jei.recipe; +import brachy.modularui.api.drawable.IRichTextBuilder; +import brachy.modularui.api.widget.ITooltip; import brachy.modularui.api.widget.IWidget; +import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; -import brachy.modularui.integration.recipeviewer.entry.EntryList; -import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; -import brachy.modularui.integration.recipeviewer.util.RecipeScreenRenderingUtil; +import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; -import brachy.modularui.widget.WidgetTree; -import brachy.modularui.widget.sizer.Area; +import brachy.modularui.screen.RichTooltip; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.navigation.ScreenPosition; import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.TooltipFlag; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; -import mezz.jei.api.gui.builder.IRecipeSlotBuilder; import mezz.jei.api.gui.builder.ITooltipBuilder; +import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; import mezz.jei.api.gui.ingredient.IRecipeSlotsView; import mezz.jei.api.gui.inputs.IJeiGuiEventListener; import mezz.jei.api.gui.widgets.IRecipeExtrasBuilder; -import mezz.jei.api.gui.widgets.IRecipeWidget; -import mezz.jei.api.ingredients.IIngredientRenderer; import mezz.jei.api.recipe.IFocusGroup; -import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.recipe.category.IRecipeCategory; import org.apache.commons.lang3.mutable.MutableInt; import org.jetbrains.annotations.ApiStatus; -import java.util.Collections; +import java.time.Duration; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.function.Function; @ApiStatus.Experimental public abstract class ModularUIRecipeCategory implements IRecipeCategory { + public static final String SCREEN_NAME_PREFIX = "jei_recipe_"; + private final LoadingCache modularScreenCache; - protected ModularUIRecipeCategory(Function wrapperFunction, Function recipeIdGetter) { + private final Function recipeUI; + private final Function recipeIdGetter; + + private boolean sizeCalculated = false; + private int displayWidth, displayHeight; + + protected ModularUIRecipeCategory(Function recipeUI, Function recipeIdGetter) { + this.recipeUI = recipeUI; + this.recipeIdGetter = recipeIdGetter; + this.modularScreenCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.SECONDS) - .maximumSize(10) + .expireAfterAccess(Duration.ofSeconds(1)) + .maximumSize(20) .build(new CacheLoader<>() { @Override public ModularScreen load(T recipe) { - IWidget widget = wrapperFunction.apply(recipe); - ResourceLocation recipeId = recipeIdGetter.apply(recipe); - - ModularPanel panel = ModularPanel.defaultPanel(recipeId.toString(), - widget.getArea().width, widget.getArea().height); - panel.child(widget); - return new ModularScreen(recipeId.getNamespace(), panel); + return ModularUIRecipeCategory.this.createScreen(recipe); } }); } @@ -70,84 +68,151 @@ private ModularScreen getModularScreen(T recipe) { return this.modularScreenCache.getUnchecked(recipe); } - private static void addJEISlot(IRecipeLayoutBuilder builder, EntryList entries, JeiRecipeViewerSlot widget, - RecipeIngredientRole role, int index) { - var type = ModularUIJeiPlugin.getRuntime().getIngredientManager() - .getIngredientTypeChecked(entries.getType()); - if (type.isEmpty()) { - return; + /** + * Calculates the size of the recipe if not already done.
+ * This should be called in subclasses' {@link #setRecipe(IRecipeLayoutBuilder, Object, IFocusGroup) setRecipe} methods. + * Otherwise, the size of ALL the recipes in the same category are calculated at once, which can make the game lag for a few seconds. + */ + protected void calculateSize(T recipe) { + if (this.sizeCalculated) return; + this.sizeCalculated = true; + IWidget ui = this.recipeUI.apply(recipe); + ResourceLocation id = this.recipeIdGetter.apply(recipe); + int w = ui.resizer().getFixedPixelWidth(), h = ui.resizer().getFixedPixelHeight(); + if (w < 0 || h < 0) { + ModularScreen screen = createScreen(ui, id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath()); + w = EmbedHandler.getEmbedWidth(screen); + h = EmbedHandler.getEmbedHeight(screen); } + this.displayWidth = w; + this.displayHeight = h; + } - Area widgetArea = widget.getArea(); - IRecipeSlotBuilder slotBuilder = builder.addSlot(role, widgetArea.x, widgetArea.y); + /** + * Return the maximum expected display width here.
+ * You should also return a per-category display width that's at most this value in {@link #getWidth(Object)}. + * @return The maximum expected display width + */ + @ApiStatus.OverrideOnly + @Override + public abstract int getWidth(); - slotBuilder.addIngredients(type.get(), entries.getStacks()); - slotBuilder.setCustomRenderer(type.get(), new IIngredientRenderer<>() { + public int getWidth(T recipe) { + calculateSize(recipe); + return this.displayWidth; + } - @Override - public void render(GuiGraphics guiGraphics, T ingredient) {} + /** + * Return the maximum expected display height here.
+ * You should also return a per-category display height that's at most this value in {@link #getHeight(Object)}. + * @return The maximum expected display height + */ + @ApiStatus.OverrideOnly + @Override + public abstract int getHeight(); - @SuppressWarnings("removal") - @Override - public List getTooltip(T ingredient, TooltipFlag tooltipFlag) { - return Collections.emptyList(); - } + public int getHeight(T recipe) { + calculateSize(recipe); + return this.displayHeight; + } - @Override - public int getWidth() { - return widgetArea.width; - } + private ModularScreen createScreen(T recipe) { + ResourceLocation id = this.recipeIdGetter.apply(recipe); + return createScreen(this.recipeUI.apply(recipe), id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath()); + } - @Override - public int getHeight() { - return widgetArea.height; - } - }); - // set slot name - slotBuilder.setSlotName("slot_" + index); + public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { + ModularPanel panel; + if (recipeUI instanceof ModularPanel panel1) { + panel = panel1; + } else { + panel = new ModularPanel<>(name); + panel.coverChildren(60, 40) + .invisible() + .child(recipeUI); + } + ModularScreen screen = ModularScreen.createEmbed(owner, panel); + screen.getContext().getUISettings().drawTooltipExternally(true); + return screen; + } + + @ApiStatus.OverrideOnly + public IWidget transformWidget(IRecipeExtrasBuilder builder, IWidget widget) { + if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; + + String name = recipeViewerSlot.getName(); + assert name != null; // the slots should always have a name assigned in createRecipeSlotForWidget() + // the JEI slot should also always exist as it's created in the same method + IRecipeSlotDrawable slot = builder.getRecipeSlots().findSlotByName(name).orElseThrow(); + + recipeViewerSlot.setSlotWidget(slot); + builder.addSlottedWidget(recipeViewerSlot, List.of(slot)); + + if (recipeViewerSlot.recipeSlotRole() == RecipeSlotRole.OUTPUT) { + // recipeViewerSlot.getSlotWidget().recipeContext(this); + } + + return recipeViewerSlot; + } + + @ApiStatus.OverrideOnly + public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget widget, T recipe, IFocusGroup focuses, int index) { + if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; + + recipeViewerSlot.setFocuses(focuses); + if (recipeViewerSlot.getName() == null) { + recipeViewerSlot.name("jei_slot_" + index); + } + + builder.addSlot(ModularUIJeiPlugin.mapToJeiRole(recipeViewerSlot.recipeSlotRole())) + .setSlotName(recipeViewerSlot.getName()); + + return recipeViewerSlot; } @Override public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { ModularScreen screen = getModularScreen(recipe); - MutableInt i = new MutableInt(0); - WidgetTree.foreachChildBFS(screen.getMainPanel(), widget -> { - if (!(widget instanceof JeiRecipeViewerSlot provider)) { - return true; - } - RecipeIngredientRole role = mapToRole(provider.recipeSlotRole()); - addJEISlot(builder, provider.getValue(), provider, role, i.getAndIncrement()); - return true; - }, true); + screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, i.getAndIncrement())); } @Override public void createRecipeExtras(IRecipeExtrasBuilder builder, T recipe, IFocusGroup focuses) { + ModularScreen screen = getModularScreen(recipe); + + screen.getMainPanel().visitTransformAllChildren(widget -> transformWidget(builder, widget)); builder.addGuiEventListener(new ModularUIGuiEventListener(recipe)); - builder.addWidget(new UIForegroundRenderWidget(recipe)); } @Override - public void draw(T recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics guiGraphics, double mouseX, double mouseY) { + public void getTooltip(ITooltipBuilder tooltipBuilder, T recipe, IRecipeSlotsView recipeSlotsView, double mouseX, double mouseY) { ModularScreen screen = getModularScreen(recipe); + if (!screen.getContext().getUISettings().drawTooltipExternally()) { + IRecipeCategory.super.getTooltip(tooltipBuilder, recipe, recipeSlotsView, mouseX, mouseY); + return; + } - RecipeScreenRenderingUtil.drawScreenBackground(guiGraphics, screen, (int) mouseX, (int) mouseY, - Minecraft.getInstance().getPartialTick()); + IWidget hovered = screen.getContext().getTopHovered(); + if (hovered instanceof ITooltip tooltip && tooltip.getTooltip() != null) { + RichTooltip richTooltip = tooltip.getTooltip(); + if (richTooltip.autoUpdate()) richTooltip.markDirty(); + richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary + + IRichTextBuilder richTextBuilder = richTooltip.getRichText(); + if (richTextBuilder instanceof RichText richText) { + for (var line : richText.getAsText()) { + // scuffed conversion, but it mostly works + line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); + } + } + } } @Override - public void getTooltip(ITooltipBuilder tooltip, T recipe, IRecipeSlotsView recipeSlotsView, double mouseX, double mouseY) { - // tooltip.clear(); - } - - public static RecipeIngredientRole mapToRole(RecipeSlotRole slotRole) { - return switch (slotRole) { - case INPUT -> RecipeIngredientRole.INPUT; - case OUTPUT -> RecipeIngredientRole.OUTPUT; - case CATALYST -> RecipeIngredientRole.CATALYST; - case RENDER_ONLY -> RecipeIngredientRole.RENDER_ONLY; - }; + public void draw(T recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) { + ModularScreen screen = getModularScreen(recipe); + EmbedHandler.drawEmbed(screen, graphics, Minecraft.getInstance().getPartialTick()); } public class ModularUIGuiEventListener implements IJeiGuiEventListener { @@ -193,24 +258,4 @@ public boolean keyPressed(double mouseX, double mouseY, int keyCode, int scanCod } } - public class UIForegroundRenderWidget implements IRecipeWidget { - - private final T recipe; - - public UIForegroundRenderWidget(T recipe) { - this.recipe = recipe; - } - - @Override - public ScreenPosition getPosition() { - return new ScreenPosition(0, 0); - } - - @Override - public void drawWidget(GuiGraphics guiGraphics, double mouseX, double mouseY) { - ModularScreen screen = getModularScreen(this.recipe); - RecipeScreenRenderingUtil.drawScreenForeground(guiGraphics, screen, (int) mouseX, (int) mouseY, - Minecraft.getInstance().getPartialTick()); - } - } } diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/package-info.java b/src/main/java/brachy/modularui/integration/jei/recipe/package-info.java index fa02528d..04b54f6a 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/package-info.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/package-info.java @@ -1,7 +1,4 @@ -@ParametersAreNonnullByDefault -@MethodsReturnNonnullByDefault +@NotNullByDefault package brachy.modularui.integration.jei.recipe; -import net.minecraft.MethodsReturnNonnullByDefault; - -import javax.annotation.ParametersAreNonnullByDefault; +import org.jetbrains.annotations.NotNullByDefault; diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeScreenRenderingUtil.java b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeScreenRenderingUtil.java deleted file mode 100644 index 6e8e6e2d..00000000 --- a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeScreenRenderingUtil.java +++ /dev/null @@ -1,75 +0,0 @@ -package brachy.modularui.integration.recipeviewer.util; - -import brachy.modularui.screen.ClientScreenHandler; -import brachy.modularui.screen.ModularScreen; - -import net.minecraft.client.gui.GuiGraphics; -import com.mojang.blaze3d.platform.Lighting; -import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraftforge.items.IItemHandlerModifiable; -import net.minecraftforge.items.wrapper.EmptyHandler; - -import org.jetbrains.annotations.ApiStatus; - -@Deprecated -@ApiStatus.Experimental -public class RecipeScreenRenderingUtil { - - public static final IItemHandlerModifiable EMPTY_ITEM_HANDLER = new EmptyHandler() { - @Override - public int getSlots() { - return 1; - } - }; - - @ApiStatus.Internal - public static void drawScreenBackground(GuiGraphics guiGraphics, ModularScreen screen, - int mouseX, int mouseY, float partialTick) { - screen.getContext().setGraphics(guiGraphics); - screen.getContext().updateState(mouseX, mouseY, partialTick); - screen.getContext().graphicsPose().pushPose(); - RenderSystem.applyModelViewMatrix(); - - // copied from ClientScreenHandler#drawScreenInternal to - // let us draw foreground elements separately after everything else. - //Stencil.reset(); - //screen.getContext().getStencil().push(screen.getScreenArea()); - - screen.render(guiGraphics, mouseX, mouseY, partialTick); - - RenderSystem.disableDepthTest(); - - ClientScreenHandler.drawVanillaElements(guiGraphics, screen.getScreenWrapper().wrappedScreen(), - mouseX, mouseY, partialTick); - - RenderSystem.enableDepthTest(); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - - //screen.getContext().getStencil().pop(); - screen.getContext().graphicsPose().popPose(); - RenderSystem.applyModelViewMatrix(); - } - - @ApiStatus.Internal - public static void drawScreenForeground(GuiGraphics guiGraphics, ModularScreen screen, - int mouseX, int mouseY, float partialTick) { - screen.getContext().setGraphics(guiGraphics); - screen.getContext().updateState(mouseX, mouseY, partialTick); - //screen.getContext().graphicsPose().pushPose(); - - // copied from ClientScreenHandler#drawScreenInternal to - // let us draw foreground elements separately after everything else. - //screen.getContext().getStencil().push(screen.getScreenArea()); - RenderSystem.disableDepthTest(); - Lighting.setupForFlatItems(); - - screen.drawForeground(guiGraphics); - - RenderSystem.enableDepthTest(); - Lighting.setupFor3DItems(); - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); - - //screen.getContext().getStencil().pop(); - //screen.getContext().graphicsPose().popPose(); - } -} diff --git a/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java b/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java index 6f387b25..710c9fa2 100644 --- a/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java +++ b/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java @@ -124,6 +124,14 @@ public static Optional> getFor(Class clazz) { return Optional.ofNullable(getForNullable(clazz)); } + public static EntryIngredient convertToReiEntry(EntryList entries, float chance) { + REIStackConverter.Converter converter = REIStackConverter.getForNullable(entries.getType()); + if (converter != null) { + return converter.convertTo(entries, chance, UnaryOperator.identity()); + } + return EntryIngredient.empty(); + } + public interface Converter { @Nullable diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 59bd3f01..d09f4f60 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -65,10 +65,9 @@ public ReiRecipeViewerSlot value(EntryList entryList) { return getThis(); } - @SuppressWarnings("unchecked") private void rebuildReiSlot() { slotWidget = Widgets.createSlot(new Point()).disableBackground(); - slotWidget.entries(convertToReiEntry(this.value, chance)); + slotWidget.entries(REIStackConverter.convertToReiEntry(this.value, chance)); if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { slotWidget.markInput(); } else if (recipeSlotRole == RecipeSlotRole.OUTPUT) { @@ -77,13 +76,6 @@ private void rebuildReiSlot() { slotWidget.unmarkInputOrOutput(); } } - private static EntryIngredient convertToReiEntry(EntryList entries, float chance) { - REIStackConverter.Converter converter = REIStackConverter.getForNullable(entries.getType()); - if (converter != null){ - return converter.convertTo(entries, chance, UnaryOperator.identity()); - } - return EntryIngredient.empty(); - } @Override public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index a93dfc9e..fa037c54 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -1,29 +1,22 @@ package brachy.modularui.integration.rei.recipe; +import brachy.modularui.api.drawable.IRichTextBuilder; import brachy.modularui.api.widget.ITooltip; import brachy.modularui.api.widget.IWidget; import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; -import brachy.modularui.integration.recipeviewer.RecipeViewerScreenWrapper; -import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; -import brachy.modularui.integration.recipeviewer.handlers.fluid.EmptyFluidTank; -import brachy.modularui.integration.recipeviewer.util.RecipeScreenRenderingUtil; -import brachy.modularui.integration.rei.REIStackConverter; import brachy.modularui.integration.rei.ReiRecipeViewerSlot; +import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; -import brachy.modularui.utils.memoization.MemoizedSupplier; -import brachy.modularui.utils.memoization.Memoizer; -import brachy.modularui.widget.WidgetTree; -import brachy.modularui.widget.sizer.Area; -import brachy.modularui.widgets.slot.FluidSlot; -import brachy.modularui.widgets.slot.ItemSlot; +import brachy.modularui.screen.RichTooltip; -import me.shedaniel.rei.api.client.gui.widgets.Slot; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import lombok.Getter; @@ -31,133 +24,182 @@ import me.shedaniel.rei.api.client.gui.widgets.Tooltip; import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds; import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.entry.EntryIngredient; -import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.function.Supplier; @ApiStatus.Experimental -public class ModularUIREIDisplay implements Display { +public abstract class ModularUIREIDisplay implements Display { + + private static final String SCREEN_NAME_PREFIX = "rei_display_"; + private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(Duration.ofSeconds(1)) + .maximumSize(20) + .build(new CacheLoader<>() { + @Override + public ModularScreen load(ModularUIREIDisplay key) { + return key.createScreen(); + } + }); + + private static ModularScreen getModularScreen(ModularUIREIDisplay display) { + return SCREEN_CACHE.getUnchecked(display); + } private final ResourceLocation recipeId; - protected final MemoizedSupplier screen; + private final Supplier recipeUI; + @Getter protected final CategoryIdentifier categoryId; - @Getter - protected final List inputEntries; - @Getter - protected final List outputEntries; - protected final List catalysts; - @Getter - protected final CategoryIdentifier categoryIdentifier; + private boolean sizeCalculated = false; + private Rectangle bounds; + private int displayWidth, displayHeight; - public ModularUIREIDisplay(ResourceLocation recipeId, Supplier widgetSupplier, CategoryIdentifier category) { + public ModularUIREIDisplay(ResourceLocation recipeId, Supplier recipeUI, CategoryIdentifier categoryId) { this.recipeId = recipeId; + this.recipeUI = recipeUI; + this.categoryId = categoryId; + } - this.inputEntries = new ArrayList<>(); - this.outputEntries = new ArrayList<>(); - this.catalysts = new ArrayList<>(); - this.categoryIdentifier = category; + /** + * Calculates the size of the recipe if not already done.
+ * This should be called in subclasses' constructors. Otherwise, the size of ALL the recipes in the same category are calculated + * at once, which can make the game lag for a few seconds. + */ + protected void calculateSize() { + if (this.sizeCalculated) return; + this.sizeCalculated = true; + + IWidget ui = this.recipeUI.get(); + int w = ui.resizer().getFixedPixelWidth(), h = ui.resizer().getFixedPixelHeight(); + if (w < 0 || h < 0) { + ModularScreen screen = createScreen(ui, this.recipeId.getNamespace(), SCREEN_NAME_PREFIX + this.recipeId.getPath()); + w = EmbedHandler.getEmbedWidth(screen); + h = EmbedHandler.getEmbedHeight(screen); + } + this.displayWidth = w; + this.displayHeight = h; + } - this.screen = Memoizer.memoize(() -> { - IWidget widget = widgetSupplier.get(); - ModularPanel panel = ModularPanel.defaultPanel(recipeId.toString(), widget.getArea().w(), widget.getArea().h()); - panel.child(widget); - return new ModularScreen(recipeId.getNamespace(), panel); - }, Duration.ofSeconds(10)); + public Rectangle getBounds() { + calculateSize(); + return this.bounds; + } - WidgetTree.foreachChildBFS(widgetSupplier.get(), widget -> { - if (!(widget instanceof ReiRecipeViewerSlot provider)) return true; + public int getDisplayWidth() { + calculateSize(); + return this.displayWidth; + } - RecipeSlotRole role = provider.recipeSlotRole(); - if (role == RecipeSlotRole.RENDER_ONLY) return true; + public int getDisplayHeight() { + calculateSize(); + return this.displayHeight; + } - REIStackConverter.Converter converter = REIStackConverter.getForNullable(provider.getValue().getType()); - if (converter == null) return true; + private ModularScreen createScreen() { + return createScreen(this.recipeUI.get(), this.recipeId.getNamespace(), SCREEN_NAME_PREFIX + this.recipeId.getPath()); + } - @SuppressWarnings({"rawtypes", "unchecked"}) - EntryIngredient ingredient = EntryIngredient.of(provider.getSlotWidget().getEntries()); + @ApiStatus.OverrideOnly + public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { + ModularPanel panel; + if (recipeUI instanceof ModularPanel panel1) { + panel = panel1; + } else { + panel = new ModularPanel<>(name); + panel.coverChildren(60, 40) + .invisible() + .child(recipeUI); + } + if (!getInputEntries().isEmpty() && !getOutputEntries().isEmpty()) { + panel = transform(panel); + } + ModularScreen screen = ModularScreen.createEmbed(owner, panel); + screen.getContext().getUISettings().drawTooltipExternally(true); + return screen; + } - switch (role) { - case INPUT -> inputEntries.add(ingredient); - case OUTPUT -> outputEntries.add(ingredient); - case CATALYST -> catalysts.add(ingredient); - } - return true; - }, true); + public ModularPanel transform(ModularPanel panel) { + Iterator in = getInputEntries().iterator(); + Iterator out = getOutputEntries().iterator(); + panel.visitTransformAllChildren(widget -> transformWidget(widget, in, out)); + return panel; + } + + @ApiStatus.OverrideOnly + public IWidget transformWidget(IWidget widget, Iterator in, Iterator out) { + if (!(widget instanceof ReiRecipeViewerSlot recipeViewerSlot)) return widget; + + if (recipeViewerSlot.recipeSlotRole() == RecipeSlotRole.OUTPUT) { + // recipeViewerSlot.getSlotWidget().recipeContext(this); + } + + return recipeViewerSlot; } @Override public Optional getDisplayLocation() { - return Optional.of(recipeId); + return Optional.of(this.recipeId); } public List createWidgets(Rectangle bounds) { List widgets = new ArrayList<>(); - widgets.add(new UIWrapperWidget()); - - WidgetTree.foreachChildBFS(this.screen.get().getMainPanel(), widget -> { - if (!(widget instanceof ReiRecipeViewerSlot provider)) return true; - - RecipeSlotRole role = provider.recipeSlotRole(); - if (role == RecipeSlotRole.RENDER_ONLY) return true; - - REIStackConverter.Converter converter = REIStackConverter.getForNullable(provider.getValue().getType()); - if (converter == null) return true; - - @SuppressWarnings({"rawtypes", "unchecked"}) - EntryIngredient ingredient = EntryIngredient.of(provider.getSlotWidget().getEntries()); - Area area = widget.getArea(); - - Slot entryWidget = provider.getSlotWidget(); -/* - if (provider.hasTooltip()) { - if (provider.tooltip().getRichText() instanceof RichText richText) { - var textList = richText.getAsText(); - entryWidget.tooltipProcessor(text -> { - textList.forEach(line -> line.map(t -> text.add((Component) t), text::add)); - return text; - }); - } - } -*/ - widgets.add(entryWidget); - return true; - }, true); - widgets.add(new UIForegroundRenderWidget()); + widgets.add(new UIWrapperWidget(this)); return widgets; } - @Override - public List getRequiredEntries() { - var required = new ArrayList<>(catalysts); - required.addAll(inputEntries); - return required; - } + public static class UIWrapperWidget extends WidgetWithBounds { - public class UIWrapperWidget extends Widget { + private final ModularUIREIDisplay display; - public UIWrapperWidget() { - ModularScreen screen = ModularUIREIDisplay.this.screen.get(); - screen.construct(new RecipeViewerScreenWrapper(screen)); + public UIWrapperWidget(ModularUIREIDisplay display) { + this.display = display; + } + + @Override + public Rectangle getBounds() { + return this.display.getBounds(); } @Override - public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { - ModularScreen screen = ModularUIREIDisplay.this.screen.get(); - RecipeScreenRenderingUtil.drawScreenBackground(guiGraphics, screen, mouseX, mouseY, partialTick); + public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + ModularScreen screen = getModularScreen(this.display); + EmbedHandler.drawEmbed(screen, graphics, partialTick); } @Override public @Nullable Tooltip getTooltip(TooltipContext context) { + ModularScreen screen = getModularScreen(this.display); + if (!screen.getContext().getUISettings().drawTooltipExternally()) { + return super.getTooltip(context); + } + + IWidget hovered = screen.getContext().getTopHovered(); + if (hovered instanceof ITooltip tooltip && tooltip.getTooltip() != null) { + RichTooltip richTooltip = tooltip.getTooltip(); + if (richTooltip.autoUpdate()) richTooltip.markDirty(); + richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary + + IRichTextBuilder richTextBuilder = richTooltip.getRichText(); + if (richTextBuilder instanceof RichText richText) { + // scuffed conversion, but it mostly works + Tooltip tooltipBuilder = Tooltip.create(context.getPoint()); + for (var line : richText.getAsText()) { + line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); + } + return tooltipBuilder; + } + } return null; } @@ -168,58 +210,42 @@ public List children() { @Override public void mouseMoved(double mouseX, double mouseY) { - //screen.get().mouseMoved(mouseX, mouseY); + //getModularScreen(this.display).mouseMoved(mouseX, mouseY); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - return screen.get().mousePressed(button); + return getModularScreen(this.display).mousePressed(button); } @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { - return screen.get().mouseReleased(button); + return getModularScreen(this.display).mouseReleased(button); } @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { - return screen.get().mouseDragged(button, dragX, dragY); + return getModularScreen(this.display).mouseDragged(button, dragX, dragY); } @Override public boolean mouseScrolled(double mouseX, double mouseY, double scrollDelta) { - return screen.get().mouseScrolled(scrollDelta); + return getModularScreen(this.display).mouseScrolled(scrollDelta); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - return screen.get().keyPressed(keyCode, scanCode, modifiers); + return getModularScreen(this.display).keyPressed(keyCode, scanCode, modifiers); } @Override public boolean keyReleased(int keyCode, int scanCode, int modifiers) { - return screen.get().keyReleased(keyCode, scanCode, modifiers); + return getModularScreen(this.display).keyReleased(keyCode, scanCode, modifiers); } @Override public boolean charTyped(char codePoint, int modifiers) { - return screen.get().charTyped(codePoint, modifiers); - } - } - - public class UIForegroundRenderWidget extends Widget { - - public UIForegroundRenderWidget() {} - - @Override - public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { - ModularScreen screen = ModularUIREIDisplay.this.screen.get(); - RecipeScreenRenderingUtil.drawScreenForeground(guiGraphics, screen, mouseX, mouseY, partialTick); - } - - @Override - public List children() { - return Collections.emptyList(); + return getModularScreen(this.display).charTyped(codePoint, modifiers); } } } diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java index 606c1d06..4a120166 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java @@ -8,11 +8,40 @@ import java.util.List; @ApiStatus.Experimental -public abstract class ModularUIREIDisplayCategory - implements DisplayCategory { +public abstract class ModularUIREIDisplayCategory implements DisplayCategory { @Override public List setupDisplay(D display, Rectangle bounds) { return display.createWidgets(bounds); } + + /** + * Returns the display-dependent width. + * + * @param display the display + * @return the display width + */ + @Override + public int getDisplayWidth(D display) { + return display.getDisplayWidth(); + } + + /** + * Return the maximum expected display height here.
+ * You should also return a per-category display height that's at most this value in {@link #getDisplayHeight(ModularUIREIDisplay)}. + * @return The maximum expected display height + */ + @ApiStatus.OverrideOnly + @Override + public abstract int getDisplayHeight(); + + /** + * Returns the display-dependent height. + * + * @param display the display + * @return the display height + */ + public int getDisplayHeight(D display) { + return display.getDisplayHeight(); + } } From 37debefd550f1f8b5478f408acfaa5b9904e6a78 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:23:43 +0300 Subject: [PATCH 07/84] Unify code style in EMI & REI plugins --- .../integration/emi/EmiRecipeViewerSlot.java | 25 ++++++----- .../integration/emi/EmiStackConverter.java | 37 ++++++++++----- .../integration/emi/ModularUIEmiPlugin.java | 2 +- .../emi/recipe/ModularUIEmiRecipe.java | 45 ++++++++++--------- .../integration/rei/REIStackConverter.java | 12 ++--- .../integration/rei/ReiRecipeViewerSlot.java | 9 +--- 6 files changed, 71 insertions(+), 59 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index fe17e07c..f7ce8db8 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -9,7 +9,6 @@ import brachy.modularui.theme.WidgetThemeEntry; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraftforge.fluids.FluidStack; @@ -21,17 +20,18 @@ import lombok.experimental.Accessors; import org.jetbrains.annotations.ApiStatus; +import java.util.function.UnaryOperator; + @ApiStatus.Experimental public class EmiRecipeViewerSlot extends RecipeViewerSlotWidget { @ApiStatus.Internal @Getter private SlotWidget slotWidget; - private int x, y; @Accessors(fluent = true) @Getter - private RecipeSlotRole recipeSlotRole; + private RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; private EntryList value; @Accessors(fluent = true) @Getter @@ -41,7 +41,6 @@ public class EmiRecipeViewerSlot extends RecipeViewerSlotWidget EmiRecipeViewerSlot value(EntryList entryList) { @SuppressWarnings("unchecked") private void rebuildEmiSlot() { - if (this.value.getType() == ItemStack.class) { - slotWidget = new SlotWidget(EmiStackConverter.ITEM.convertTo((EntryList) this.value, chance), 0, 0); - } else if (this.value.getType() == FluidStack.class) { - slotWidget = new TankWidget(EmiStackConverter.FLUID.convertTo((EntryList) this.value, chance), 0, 0, 18, 18, 1); + if (this.value.getType() == FluidStack.class) { + // special case fluid slots + EmiIngredient ingredient = EmiStackConverter.FLUID.convertTo((EntryList) this.value, chance, UnaryOperator.identity()); + slotWidget = new TankWidget(ingredient, 0, 0, 18, 18, 1); + } else { + slotWidget = new SlotWidget(EmiStackConverter.convertToEmiEntry(this.value, chance), 0, 0); } slotWidget.drawBack(false); + slotWidget.catalyst(recipeSlotRole == RecipeSlotRole.CATALYST); } @Override public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - context.getGraphics().pose().translate(-this.x, -this.y, 0); + context.graphicsPose().pushPose(); + context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); - context.getGraphics().pose().translate(this.x, this.y, 0); + context.graphicsPose().popPose(); } @Override diff --git a/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java b/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java index 14724a23..44055ec0 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Optional; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -53,17 +54,19 @@ private static EmiIngredient toEMIIngredient(Stream stream) { } @Override - public EmiIngredient convertTo(EntryList stack, float chance) { + public EmiIngredient convertTo(EntryList stack, float chance, UnaryOperator mapper) { if (stack.isEmpty()) { return EmiStack.EMPTY; } if (stack instanceof ItemStackList stackList) { - return toEMIIngredient(stackList.stream()).setChance(chance); + return toEMIIngredient(stackList.stream().map(mapper)).setChance(chance); } else if (stack instanceof ItemTagList tagList) { return EmiIngredient.of(tagList.getEntries().stream() - .map(ItemTagList.ItemTagEntry::stacks) - .map(stream -> toEMIIngredient(stream)) - .collect(Collectors.toList()), tagList.getEntries().get(0).amount()).setChance(chance); + .map(ItemTagList.ItemTagEntry::stacks) + .map(stream -> toEMIIngredient(stream.map(mapper))) + .collect(Collectors.toList()), + tagList.getEntries().get(0).amount()) + .setChance(chance); } return EmiStack.EMPTY; } @@ -90,17 +93,19 @@ private static EmiIngredient toEMIIngredient(Stream stream) { } @Override - public EmiIngredient convertTo(EntryList stack, float chance) { + public EmiIngredient convertTo(EntryList stack, float chance, UnaryOperator mapper) { if (stack.isEmpty()) { return EmiStack.EMPTY; } if (stack instanceof FluidStackList stackList) { - return toEMIIngredient(stackList.stream()).setChance(chance); + return toEMIIngredient(stackList.stream().map(mapper)).setChance(chance); } else if (stack instanceof FluidTagList tagList) { return EmiIngredient.of(tagList.getEntries().stream() - .map(FluidTagList.FluidTagEntry::stacks) - .map(stream -> toEMIIngredient(stream)) - .collect(Collectors.toList()), tagList.getEntries().get(0).amount()).setChance(chance); + .map(FluidTagList.FluidTagEntry::stacks) + .map(stream -> toEMIIngredient(stream.map(mapper))) + .collect(Collectors.toList()), + tagList.getEntries().get(0).amount()) + .setChance(chance); } return EmiStack.EMPTY; } @@ -121,15 +126,23 @@ public static Optional> getFor(Class clazz) { return Optional.ofNullable(getForNullable(clazz)); } + public static EmiIngredient convertToEmiEntry(EntryList entries, float chance) { + Converter converter = getForNullable(entries.getType()); + if (converter != null) { + return converter.convertTo(entries, chance, UnaryOperator.identity()); + } + return EmiStack.EMPTY; + } + public interface Converter { @Nullable T convertFrom(EmiStack stack); - EmiIngredient convertTo(EntryList stack, float chance); + EmiIngredient convertTo(EntryList stack, float chance, UnaryOperator mapper); default EmiIngredient convertTo(IngredientProvider slot) { - return this.convertTo(slot.getIngredients(), slot.chance()); + return this.convertTo(slot.getIngredients(), slot.chance(), slot.renderMappingFunction()); } } } diff --git a/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java b/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java index 0cf35aeb..88e1726c 100644 --- a/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java +++ b/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java @@ -18,9 +18,9 @@ public class ModularUIEmiPlugin implements EmiPlugin { public void register(EmiRegistry registry) { EmiScreenHandler.register(ScreenWrapper.class, registry); EmiScreenHandler.register(ContainerScreenWrapper.class, registry); + if (ModularUI.isDev()) { TestMachine.EMI.register(registry); } } - } diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 702ab7b3..e404a455 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -1,6 +1,5 @@ package brachy.modularui.integration.emi.recipe; -import brachy.modularui.api.drawable.IRichTextBuilder; import brachy.modularui.api.widget.ITooltip; import brachy.modularui.api.widget.IWidget; import brachy.modularui.drawable.text.RichText; @@ -37,6 +36,7 @@ @ApiStatus.Experimental public abstract class ModularUIEmiRecipe implements EmiRecipe { + private static final String SCREEN_NAME_PREFIX = "emi_recipe_"; private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() .expireAfterAccess(Duration.ofSeconds(1)) .maximumSize(20) @@ -47,6 +47,10 @@ public ModularScreen load(ModularUIEmiRecipe key) { } }); + private static ModularScreen getModularScreen(ModularUIEmiRecipe recipe) { + return SCREEN_CACHE.getUnchecked(recipe); + } + @Getter private final ResourceLocation id; private final Supplier recipeUI; @@ -69,8 +73,9 @@ public ModularUIEmiRecipe(ResourceLocation recipeId, int width, int height, Supp } /** - * Calculates the size of the recipe, if not already done. This should be called in the constructor of sub-classes. - * Otherwise, the size of ALL the recipes in the same category are calculated all at once, which can make the game for a few seconds. + * Calculates the size of the recipe if not already done.
+ * This should be called in subclasses' constructors. Otherwise, the size of ALL the recipes in the same category are calculated + * at once, which can make the game lag for a few seconds. */ protected void calculateSize() { if (this.sizeCalculated) return; @@ -78,7 +83,7 @@ protected void calculateSize() { IWidget ui = this.recipeUI.get(); int w = ui.resizer().getFixedPixelWidth(), h = ui.resizer().getFixedPixelHeight(); if (w < 0 || h < 0) { - ModularScreen screen = createScreen(ui, this.id.getNamespace(), "emi_recipe_" + this.id.getPath()); + ModularScreen screen = createScreen(ui, this.id.getNamespace(), SCREEN_NAME_PREFIX + this.id.getPath()); w = EmbedHandler.getEmbedWidth(screen); h = EmbedHandler.getEmbedHeight(screen); } @@ -89,23 +94,23 @@ protected void calculateSize() { public Bounds getBounds() { calculateSize(); - return bounds; + return this.bounds; } @Override - public int getDisplayHeight() { + public int getDisplayWidth() { calculateSize(); - return displayHeight; + return this.displayWidth; } @Override - public int getDisplayWidth() { + public int getDisplayHeight() { calculateSize(); - return displayWidth; + return this.displayHeight; } private ModularScreen createScreen() { - return createScreen(this.recipeUI.get(), this.id.getNamespace(), "emi_recipe_" + this.id.getPath()); + return createScreen(this.recipeUI.get(), this.id.getNamespace(), SCREEN_NAME_PREFIX + this.id.getPath()); } public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { @@ -165,51 +170,51 @@ public Bounds getBounds() { @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - ModularScreen screen = SCREEN_CACHE.getUnchecked(this.recipe); + ModularScreen screen = getModularScreen(this.recipe); EmbedHandler.drawEmbed(screen, graphics, partialTick, r -> !(r instanceof SizedButtonWidget)); } @Override public List getTooltip(int mouseX, int mouseY) { - ModularScreen screen = SCREEN_CACHE.getUnchecked(this.recipe); + ModularScreen screen = getModularScreen(this.recipe); if (!screen.getContext().getUISettings().drawTooltipExternally()) { return super.getTooltip(mouseX, mouseY); } + IWidget hovered = screen.getContext().getTopHovered(); if (hovered instanceof ITooltip tooltip && tooltip.getTooltip() != null) { RichTooltip richTooltip = tooltip.getTooltip(); if (richTooltip.autoUpdate()) richTooltip.markDirty(); richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary - IRichTextBuilder richTextBuilder = richTooltip.getRichText(); - if (richTextBuilder instanceof RichText richText) { + + if (richTooltip.getRichText() instanceof RichText richText) { // scuffed conversion, but it mostly works return richText.getAsText().toClientTooltipComponents(); } - return List.of(); } return List.of(); } @Override public boolean mouseClicked(int mouseX, int mouseY, int button) { - return SCREEN_CACHE.getUnchecked(this.recipe).mousePressed(button); + return getModularScreen(this.recipe).mousePressed(button); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - return SCREEN_CACHE.getUnchecked(this.recipe).keyPressed(keyCode, scanCode, modifiers); + return getModularScreen(this.recipe).keyPressed(keyCode, scanCode, modifiers); } public boolean mouseScrolled(double mouseX, double mouseY, double delta) { - return SCREEN_CACHE.getUnchecked(this.recipe).mouseScrolled(delta); + return getModularScreen(this.recipe).mouseScrolled(delta); } public boolean mouseDragged(int button, double dragX, double dragY) { - return SCREEN_CACHE.getUnchecked(this.recipe).mouseDragged(button, dragX, dragY); + return getModularScreen(this.recipe).mouseDragged(button, dragX, dragY); } public boolean mouseReleased(int button) { - return SCREEN_CACHE.getUnchecked(this.recipe).mouseReleased(button); + return getModularScreen(this.recipe).mouseReleased(button); } } } diff --git a/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java b/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java index 710c9fa2..d41aa16c 100644 --- a/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java +++ b/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java @@ -44,14 +44,11 @@ public class REIStackConverter { } private static EntryIngredient toREIIngredient(Stream stream) { - return EntryIngredient.of(stream - .map(EntryStacks::of) - .toList()); + return EntryIngredient.of(stream.map(EntryStacks::of).toList()); } @Override - public EntryIngredient convertTo(EntryList stack, float chance, - UnaryOperator mapper) { + public EntryIngredient convertTo(EntryList stack, float chance, UnaryOperator mapper) { if (stack.isEmpty()) { return EntryIngredient.empty(); } @@ -91,8 +88,7 @@ private static EntryIngredient toREIIngredient(Stream stream) { } @Override - public EntryIngredient convertTo(EntryList stack, float chance, - UnaryOperator mapper) { + public EntryIngredient convertTo(EntryList stack, float chance, UnaryOperator mapper) { if (stack.isEmpty()) { return EntryIngredient.empty(); } @@ -101,7 +97,7 @@ public EntryIngredient convertTo(EntryList stack, float chance, } else if (stack instanceof FluidTagList tagList) { return EntryIngredient.of(tagList.getEntries().stream() .map(FluidTagList.FluidTagEntry::stacks) - .flatMap(val -> toREIIngredient(val.map(mapper)).stream()) + .flatMap(stream -> toREIIngredient(stream.map(mapper)).stream()) .collect(Collectors.toList())); } return EntryIngredient.empty(); diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index d09f4f60..ddec7c6b 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -15,22 +15,18 @@ import me.shedaniel.math.Point; import me.shedaniel.rei.api.client.gui.widgets.Slot; import me.shedaniel.rei.api.client.gui.widgets.Widgets; -import me.shedaniel.rei.api.common.entry.EntryIngredient; import org.jetbrains.annotations.ApiStatus; -import java.util.function.UnaryOperator; - @ApiStatus.Experimental public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget { @ApiStatus.Internal @Getter private Slot slotWidget; - private int x, y; @Accessors(fluent = true) @Getter - private RecipeSlotRole recipeSlotRole; + private RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; @Getter private EntryList value; @Accessors(fluent = true) @@ -41,7 +37,6 @@ public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget widgetTheme) { context.getGraphics().pose().pushPose(); - context.getGraphics().pose().translate(-this.x, -this.y, 0); + context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); context.getGraphics().pose().popPose(); } From b326221ff8685148a7464398f861b07e7b9e483a Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:23:56 +0300 Subject: [PATCH 08/84] fix onMousePressed bug --- .../brachy/modularui/integration/emi/EmiRecipeViewerSlot.java | 3 +-- .../brachy/modularui/integration/rei/ReiRecipeViewerSlot.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index f7ce8db8..9b57acf4 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -94,8 +94,7 @@ public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { @Override public Result onMousePressed(int button) { - this.slotWidget.mouseClicked(getContext().getMouseX(), getContext().getAbsMouseY(), button); - return Result.SUCCESS; + return this.slotWidget.mouseClicked(getContext().getMouseX(), getContext().getMouseY(), button) ? Result.SUCCESS : Result.ACCEPT; } @Override diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index ddec7c6b..ef0d4dc3 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -82,8 +82,7 @@ public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { @Override public Result onMousePressed(int button) { - this.slotWidget.mouseClicked(getContext().getMouseX(), getContext().getAbsMouseY(), button); - return Result.SUCCESS; + return this.slotWidget.mouseClicked(getContext().getMouseX(), getContext().getMouseY(), button) ? Result.SUCCESS : Result.ACCEPT; } @Override From f2a6e5254b51cf5689facbdb1394ef623e176ac4 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:25:13 +0300 Subject: [PATCH 09/84] Add mixins for per-recipe display sizing in JEI & REI (EMI already supports this) --- .../modularui/core/ModularUIMixinPlugin.java | 78 +++++++++++++++++++ .../mixins/jei/RecipeLayoutBuilderMixin.java | 40 ++++++++++ .../core/mixins/jei/RecipeLayoutMixin.java | 35 +++++++++ .../rei/DefaultDisplayViewingScreenMixin.java | 69 ++++++++++++++++ src/main/resources/modularui.mixins.json | 8 +- 5 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 src/main/java/brachy/modularui/core/ModularUIMixinPlugin.java create mode 100644 src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderMixin.java create mode 100644 src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java create mode 100644 src/main/java/brachy/modularui/core/mixins/rei/DefaultDisplayViewingScreenMixin.java diff --git a/src/main/java/brachy/modularui/core/ModularUIMixinPlugin.java b/src/main/java/brachy/modularui/core/ModularUIMixinPlugin.java new file mode 100644 index 00000000..e11a759b --- /dev/null +++ b/src/main/java/brachy/modularui/core/ModularUIMixinPlugin.java @@ -0,0 +1,78 @@ +package brachy.modularui.core; + +import net.minecraftforge.fml.ModLoader; +import net.minecraftforge.fml.loading.FMLLoader; + +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ModularUIMixinPlugin implements IMixinConfigPlugin { + + private static final String MIXIN_PACKAGE = "brachy.modularui.core.mixins."; + private static final Map MOD_COMPAT_MIXINS = new HashMap<>(); + + private static final String DEV_PACKAGE = "dev."; + + static { + MOD_COMPAT_MIXINS.put("roughlyenoughitems", "rei"); + MOD_COMPAT_MIXINS.put("emi", "emi"); + MOD_COMPAT_MIXINS.put("jei", "jei"); + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + if (!mixinClassName.startsWith(MIXIN_PACKAGE)) { + // skip checking mixins that aren't in our package + // this should never happen, but better safe than sorry + return true; + } + if (!FMLLoader.getLoadingModList().getErrors().isEmpty() || !ModLoader.isLoadingStateValid()) { + // stop processing mixins if we have load errors to avoid getting bad crash reports in our issues + return false; + } + mixinClassName = mixinClassName.substring(MIXIN_PACKAGE.length()); + + if (mixinClassName.startsWith(DEV_PACKAGE)) { + // don't load dev-only mixins in prod + return !FMLLoader.isProduction(); + } + for (var compatMod : MOD_COMPAT_MIXINS.entrySet()) { + if (mixinClassName.startsWith(compatMod.getValue())) { + return isModLoaded(compatMod.getKey()); + } + } + return true; + } + + @Override + public void onLoad(String mixinPackage) {} + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) {} + + @Override + public List getMixins() { + return null; + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} + + private static boolean isModLoaded(String modId) { + return FMLLoader.getLoadingModList().getModFileById(modId) != null; + } +} diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderMixin.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderMixin.java new file mode 100644 index 00000000..e299bb4d --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderMixin.java @@ -0,0 +1,40 @@ +package brachy.modularui.core.mixins.jei; + +import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.recipe.category.IRecipeCategory; +import mezz.jei.library.gui.recipes.layout.builder.RecipeLayoutBuilder; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(value = RecipeLayoutBuilder.class, remap = false) +public abstract class RecipeLayoutBuilderMixin implements IRecipeLayoutBuilder { + + @Shadow + private @Final T recipe; + + @Shadow + private @Final IRecipeCategory recipeCategory; + + @ModifyExpressionValue(method = { "createShapelessIcon", "getRecipeTransferButtonPosition" }, + at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getWidth()I"), require = 0) + private int modularui$makeWidthRecipeDependent(int original) { + if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + return muiCategory.getWidth(this.recipe); + } + return original; + } + + @ModifyExpressionValue(method = { "createShapelessIcon", "getRecipeTransferButtonPosition" }, + at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getHeight()I"), require = 0) + private int modularui$makeHeightRecipeDependent(int original) { + if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + return muiCategory.getHeight(recipe); + } + return original; + } +} diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java new file mode 100644 index 00000000..c172223e --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java @@ -0,0 +1,35 @@ +package brachy.modularui.core.mixins.jei; + +import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +import mezz.jei.api.gui.IRecipeLayoutDrawable; +import mezz.jei.api.recipe.category.IRecipeCategory; +import mezz.jei.library.gui.recipes.RecipeLayout; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(value = RecipeLayout.class, remap = false) +public abstract class RecipeLayoutMixin implements IRecipeLayoutDrawable { + + @ModifyExpressionValue(method = "", at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getWidth()I"), require = 0) + private int modularui$makeWidthRecipeDependent(int original, + @Local(argsOnly = true) IRecipeCategory recipeCategory, + @Local(argsOnly = true) R recipe) { + if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + return muiCategory.getWidth(recipe); + } + return original; + } + + @ModifyExpressionValue(method = "", at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getHeight()I"), require = 0) + private int modularui$makeHeightRecipeDependent(int original, + @Local(argsOnly = true) IRecipeCategory recipeCategory, + @Local(argsOnly = true) R recipe) { + if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + return muiCategory.getHeight(recipe); + } + return original; + } +} diff --git a/src/main/java/brachy/modularui/core/mixins/rei/DefaultDisplayViewingScreenMixin.java b/src/main/java/brachy/modularui/core/mixins/rei/DefaultDisplayViewingScreenMixin.java new file mode 100644 index 00000000..c40c5c86 --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/rei/DefaultDisplayViewingScreenMixin.java @@ -0,0 +1,69 @@ +package brachy.modularui.core.mixins.rei; + +import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; +import brachy.modularui.integration.rei.recipe.ModularUIREIDisplayCategory; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalIntRef; +import me.shedaniel.rei.api.client.registry.display.DisplayCategory; +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.impl.client.gui.screen.AbstractDisplayViewingScreen; +import me.shedaniel.rei.impl.client.gui.screen.DefaultDisplayViewingScreen; +import me.shedaniel.rei.impl.display.DisplaySpec; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.function.ToIntFunction; + +@SuppressWarnings("UnstableApiUsage") +@Mixin(value = DefaultDisplayViewingScreen.class, remap = false) +public abstract class DefaultDisplayViewingScreenMixin extends AbstractDisplayViewingScreen { + + protected DefaultDisplayViewingScreenMixin(Map, List> categoryMap, + @Nullable CategoryIdentifier category) { + super(categoryMap, category); + } + + @WrapOperation(method = "lambda$checkExportDisplays$18", at = @At(value = "INVOKE", target = "Lme/shedaniel/rei/api/client/registry/display/DisplayCategory;getDisplayHeight()I")) + private int modularui$forcePerDisplayHeightInExport(DisplayCategory category, + Operation original, + @Local(name = "display") Display display) { + return modularui$calculateCustomDisplayHeight(getCurrentCategory(), display, original::call); + } + + @Inject(method = "initDisplays", + at = @At(value = "INVOKE_ASSIGN", target = "Lme/shedaniel/rei/api/client/registry/display/DisplayCategory;getDisplayWidth(Lme/shedaniel/rei/api/common/display/Display;)I")) + private void modularui$perDisplayHeight(CallbackInfo ci, + @Local(name = "displayHeight") LocalIntRef displayHeight, + @Local(name = "displaySupplier") Supplier displaySupplier) { + displayHeight.set(modularui$calculateCustomDisplayHeight(getCurrentCategory(), displaySupplier.get(), null)); + } + + @SuppressWarnings("unchecked") + @Unique + private static int modularui$calculateCustomDisplayHeight(DisplayCategory category, Display display, + @Nullable ToIntFunction> originalValue) { + if (category instanceof ModularUIREIDisplayCategory muiCategory && display instanceof ModularUIREIDisplay muiDisplay) { + return ((ModularUIREIDisplayCategory) muiCategory).getDisplayHeight(muiDisplay); + } else { + // if this isn't a MUI display, set it back to the original value + // (I think it's possible that an MUI category has a non-MUI display in it? not sure tho.) + if (originalValue == null) { + // originalValue == null means we can't trust it and should get the actual original value + return category.getDisplayHeight(); + } else { + return originalValue.applyAsInt(category); + } + } + } +} diff --git a/src/main/resources/modularui.mixins.json b/src/main/resources/modularui.mixins.json index a5f021b2..dd171a14 100755 --- a/src/main/resources/modularui.mixins.json +++ b/src/main/resources/modularui.mixins.json @@ -4,6 +4,7 @@ "refmap": "modularui.refmap.json", "package": "brachy.modularui.core.mixins", "compatibilityLevel": "JAVA_17", + "plugin": "brachy.modularui.core.ModularUIMixinPlugin", "client": [ "client.AbstractContainerScreenAccessor", "client.AbstractContainerScreenMixin", @@ -11,7 +12,8 @@ "client.ScreenAccessor", "client.StringSplitterAccessor", "emi.RecipeScreenMixin", - "jei.IngredientListOverlayAccessor" + "jei.IngredientListOverlayAccessor", + "rei.DefaultDisplayViewingScreenMixin" ], "mixins": [ "common.AbstractContainerMenuAccessor", @@ -19,7 +21,9 @@ "common.ComponentMixin", "common.ComponentSerializerMixin", "common.SlotAccessor", - "common.TextColorMixin" + "common.TextColorMixin", + "jei.RecipeLayoutBuilderMixin", + "jei.RecipeLayoutMixin" ], "injectors": { "defaultRequire": 1, From 1c8cbf702f67d29b9b22f2e7962ba6a2acf800e9 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Sat, 1 Aug 2026 23:51:22 -0400 Subject: [PATCH 10/84] fixed compile errors --- .../modularui/integration/jei/JeiRecipeViewerSlot.java | 10 +++++----- .../modularui/integration/rei/ReiRecipeViewerSlot.java | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 118d4ff1..824457f5 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -39,7 +39,7 @@ public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget value; + @Getter private EntryList entryList; @Accessors(fluent = true) @Getter @Setter private float chance = 1f; @@ -58,9 +58,9 @@ public JeiRecipeViewerSlot recipeSlotRole(RecipeSlotRole recipeSlotRole) { @Override public JeiRecipeViewerSlot value(EntryList entryList) { - this.value = entryList; + this.entryList = entryList; rebuildJeiSlot(); - if (this.value.getType() == FluidStack.class) { + if (this.entryList.getType() == FluidStack.class) { background(GuiTextures.SLOT_FLUID); } else { background(GuiTextures.SLOT_ITEM); // TODO other types @@ -74,10 +74,10 @@ private void rebuildJeiSlot() { // kinda meh solution. this isn't API, but I can't find a good way to do this within the API. RecipeSlotBuilder builder = new RecipeSlotBuilder(ingredientManager, 0, ModularUIJeiPlugin.mapToJeiRole(this.recipeSlotRole)); - JeiRecipeViewerSlot.addTypedIngredients(this.value, builder); + JeiRecipeViewerSlot.addTypedIngredients(this.entryList, builder); builder.setPosition(this.getArea().x, this.getArea().y); - if (this.value.getType() == FluidStack.class) { + if (this.entryList.getType() == FluidStack.class) { // special case fluid slots (this is why we can't have nice things.) builder.setFluidRenderer(1, false, 18, 18); } diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index ef0d4dc3..c6f2e993 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -28,7 +28,7 @@ public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget value; + private EntryList entryList; @Accessors(fluent = true) @Getter @Setter @@ -50,9 +50,9 @@ public ReiRecipeViewerSlot recipeSlotRole(RecipeSlotRole recipeSlotRole) { @Override public ReiRecipeViewerSlot value(EntryList entryList) { - this.value = entryList; + this.entryList = entryList; rebuildReiSlot(); - if (this.value.getType() == FluidStack.class) { + if (this.entryList.getType() == FluidStack.class) { background(GuiTextures.SLOT_FLUID); } else { background(GuiTextures.SLOT_ITEM); // TODO other types @@ -62,7 +62,7 @@ public ReiRecipeViewerSlot value(EntryList entryList) { private void rebuildReiSlot() { slotWidget = Widgets.createSlot(new Point()).disableBackground(); - slotWidget.entries(REIStackConverter.convertToReiEntry(this.value, chance)); + slotWidget.entries(REIStackConverter.convertToReiEntry(this.entryList, chance)); if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { slotWidget.markInput(); } else if (recipeSlotRole == RecipeSlotRole.OUTPUT) { From f927474c596a457815fe2188cc73515b25ee9ad3 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 13:22:00 +0300 Subject: [PATCH 11/84] Move fields and methods common to all 3 RecipeViewerSlotWidget impls into the parent class --- .../integration/emi/EmiRecipeViewerSlot.java | 51 +++---------------- .../integration/jei/JeiRecipeViewerSlot.java | 41 ++------------- .../recipeviewer/RecipeViewerSlotWidget.java | 43 ++++++++++++++-- .../integration/rei/ReiRecipeViewerSlot.java | 49 ++---------------- 4 files changed, 58 insertions(+), 126 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index 9b57acf4..32ed2302 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -1,12 +1,10 @@ package brachy.modularui.integration.emi; import brachy.modularui.drawable.ClientTooltipComponentIcon; -import brachy.modularui.drawable.GuiTextures; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.entry.EntryList; import brachy.modularui.screen.viewport.ModularGuiContext; -import brachy.modularui.theme.WidgetThemeEntry; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.world.item.crafting.Ingredient; @@ -16,8 +14,6 @@ import dev.emi.emi.api.widget.SlotWidget; import dev.emi.emi.api.widget.TankWidget; import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; import org.jetbrains.annotations.ApiStatus; import java.util.function.UnaryOperator; @@ -26,21 +22,11 @@ public class EmiRecipeViewerSlot extends RecipeViewerSlotWidget { @ApiStatus.Internal - @Getter - private SlotWidget slotWidget; - - @Accessors(fluent = true) - @Getter - private RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; - private EntryList value; - @Accessors(fluent = true) - @Getter - @Setter - private float chance = 1f; + @Getter private SlotWidget slotWidget; public EmiRecipeViewerSlot() { super(); - slotWidget = new SlotWidget(EmiIngredient.of(Ingredient.EMPTY), 0, 0); + this.slotWidget = new SlotWidget(EmiIngredient.of(Ingredient.EMPTY), 0, 0); size(18, 18); @@ -52,44 +38,23 @@ public EmiRecipeViewerSlot() { }); } - @Override - public EmiRecipeViewerSlot recipeSlotRole(RecipeSlotRole recipeSlotRole) { - this.recipeSlotRole = recipeSlotRole; - rebuildEmiSlot(); - return getThis(); - } - - @Override - public EmiRecipeViewerSlot value(EntryList entryList) { - this.value = entryList; - rebuildEmiSlot(); - if (this.value.getType() == FluidStack.class) { - background(GuiTextures.SLOT_FLUID); - } else { - background(GuiTextures.SLOT_ITEM); // TODO other types - } - return getThis(); - } - @SuppressWarnings("unchecked") - private void rebuildEmiSlot() { - if (this.value.getType() == FluidStack.class) { + @Override + protected void rebuildRealSlot() { + if (this.entries.getType() == FluidStack.class) { // special case fluid slots - EmiIngredient ingredient = EmiStackConverter.FLUID.convertTo((EntryList) this.value, chance, UnaryOperator.identity()); + EmiIngredient ingredient = EmiStackConverter.FLUID.convertTo((EntryList) this.entries, chance, UnaryOperator.identity()); slotWidget = new TankWidget(ingredient, 0, 0, 18, 18, 1); } else { - slotWidget = new SlotWidget(EmiStackConverter.convertToEmiEntry(this.value, chance), 0, 0); + slotWidget = new SlotWidget(EmiStackConverter.convertToEmiEntry(this.entries, chance), 0, 0); } slotWidget.drawBack(false); slotWidget.catalyst(recipeSlotRole == RecipeSlotRole.CATALYST); } @Override - public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - context.graphicsPose().pushPose(); - context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); + public void drawRealSlot(ModularGuiContext context) { this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); - context.graphicsPose().popPose(); } @Override diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 824457f5..941f96db 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -1,7 +1,5 @@ package brachy.modularui.integration.jei; -import brachy.modularui.drawable.GuiTextures; -import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.entry.EntryList; @@ -10,11 +8,9 @@ import net.minecraftforge.fluids.FluidStack; import brachy.modularui.screen.viewport.ModularGuiContext; -import brachy.modularui.theme.WidgetThemeEntry; import lombok.Getter; import lombok.Setter; -import lombok.experimental.Accessors; import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; import mezz.jei.api.gui.inputs.RecipeSlotUnderMouse; import mezz.jei.api.gui.widgets.ISlottedRecipeWidget; @@ -37,47 +33,23 @@ public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget entryList; - @Accessors(fluent = true) - @Getter @Setter private float chance = 1f; - public JeiRecipeViewerSlot() { super(); size(18, 18); } - @Override - public JeiRecipeViewerSlot recipeSlotRole(RecipeSlotRole recipeSlotRole) { - this.recipeSlotRole = recipeSlotRole; - rebuildJeiSlot(); - return getThis(); - } - - @Override - public JeiRecipeViewerSlot value(EntryList entryList) { - this.entryList = entryList; - rebuildJeiSlot(); - if (this.entryList.getType() == FluidStack.class) { - background(GuiTextures.SLOT_FLUID); - } else { - background(GuiTextures.SLOT_ITEM); // TODO other types - } - return getThis(); - } - // TODO make sure this doesn't break everything (for example: search). It shouldn't but I'm not 100% on that. - private void rebuildJeiSlot() { + @Override + protected void rebuildRealSlot() { IIngredientManager ingredientManager = ModularUIJeiPlugin.getRuntime().getIngredientManager(); // kinda meh solution. this isn't API, but I can't find a good way to do this within the API. RecipeSlotBuilder builder = new RecipeSlotBuilder(ingredientManager, 0, ModularUIJeiPlugin.mapToJeiRole(this.recipeSlotRole)); - JeiRecipeViewerSlot.addTypedIngredients(this.entryList, builder); + JeiRecipeViewerSlot.addTypedIngredients(this.entries, builder); builder.setPosition(this.getArea().x, this.getArea().y); - if (this.entryList.getType() == FluidStack.class) { + if (this.entries.getType() == FluidStack.class) { // special case fluid slots (this is why we can't have nice things.) builder.setFluidRenderer(1, false, 18, 18); } @@ -95,11 +67,8 @@ private static void addTypedIngredients(EntryList entries, RecipeSlotBuil } @Override - public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - context.graphicsPose().pushPose(); - context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); + public void drawRealSlot(ModularGuiContext context) { this.slotWidget.draw(context.getGraphics()); - context.graphicsPose().popPose(); } /* @Override diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java index d11abcd2..da02b54c 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java @@ -2,25 +2,52 @@ import brachy.modularui.ModularUI; import brachy.modularui.api.widget.Interactable; +import brachy.modularui.drawable.GuiTextures; import brachy.modularui.integration.emi.EmiRecipeViewerSlot; import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.recipeviewer.entry.EntryList; import brachy.modularui.integration.recipeviewer.entry.fluid.FluidStackList; import brachy.modularui.integration.recipeviewer.entry.item.ItemStackList; +import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; import brachy.modularui.integration.rei.ReiRecipeViewerSlot; +import brachy.modularui.screen.viewport.ModularGuiContext; +import brachy.modularui.theme.WidgetThemeEntry; import brachy.modularui.widget.Widget; import net.minecraft.world.item.ItemStack; import net.minecraftforge.fluids.FluidStack; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Experimental public abstract class RecipeViewerSlotWidget> extends Widget implements Interactable { - public abstract W recipeSlotRole(RecipeSlotRole recipeSlotRole); + @Accessors(fluent = true) + @Getter @Setter protected float chance = 1f; - public abstract W value(EntryList entryList); + @Accessors(fluent = true) + @Getter protected RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; + protected EntryList entries; + + public W recipeSlotRole(RecipeSlotRole recipeSlotRole) { + this.recipeSlotRole = recipeSlotRole; + rebuildRealSlot(); + return getThis(); + } + + public W value(EntryList entryList) { + this.entries = entryList; + rebuildRealSlot(); + if (this.entries.getType() == FluidStack.class) { + background(GuiTextures.SLOT_FLUID); + } else { + background(GuiTextures.SLOT_ITEM); // TODO other types + } + return getThis(); + } public W value(ItemStack stack) { return value(ItemStackList.of(stack)); @@ -30,7 +57,17 @@ public W value(FluidStack stack) { return value(FluidStackList.of(stack)); } - public abstract W chance(float chance); + protected abstract void rebuildRealSlot(); + + protected abstract void drawRealSlot(ModularGuiContext context); + + @Override + public final void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + context.graphicsPose().pushPose(); + context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); + drawRealSlot(context); + context.graphicsPose().popPose(); + } public static RecipeViewerSlotWidget create() { if (!ModularUI.Mods.isRecipeViewerLoaded()) { diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index c6f2e993..5d93dec5 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -1,17 +1,10 @@ package brachy.modularui.integration.rei; -import brachy.modularui.drawable.GuiTextures; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; -import brachy.modularui.integration.recipeviewer.entry.EntryList; import brachy.modularui.screen.viewport.ModularGuiContext; -import brachy.modularui.theme.WidgetThemeEntry; - -import net.minecraftforge.fluids.FluidStack; import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; import me.shedaniel.math.Point; import me.shedaniel.rei.api.client.gui.widgets.Slot; import me.shedaniel.rei.api.client.gui.widgets.Widgets; @@ -21,48 +14,19 @@ public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget { @ApiStatus.Internal - @Getter - private Slot slotWidget; - - @Accessors(fluent = true) - @Getter - private RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; - @Getter - private EntryList entryList; - @Accessors(fluent = true) - @Getter - @Setter - private float chance = 1f; + @Getter private Slot slotWidget; public ReiRecipeViewerSlot() { super(); - slotWidget = Widgets.createSlot(new Point()).disableBackground(); + this.slotWidget = Widgets.createSlot(new Point()).disableBackground(); size(18, 18); } @Override - public ReiRecipeViewerSlot recipeSlotRole(RecipeSlotRole recipeSlotRole) { - this.recipeSlotRole = recipeSlotRole; - rebuildReiSlot(); - return getThis(); - } - - @Override - public ReiRecipeViewerSlot value(EntryList entryList) { - this.entryList = entryList; - rebuildReiSlot(); - if (this.entryList.getType() == FluidStack.class) { - background(GuiTextures.SLOT_FLUID); - } else { - background(GuiTextures.SLOT_ITEM); // TODO other types - } - return getThis(); - } - - private void rebuildReiSlot() { + protected void rebuildRealSlot() { slotWidget = Widgets.createSlot(new Point()).disableBackground(); - slotWidget.entries(REIStackConverter.convertToReiEntry(this.entryList, chance)); + slotWidget.entries(REIStackConverter.convertToReiEntry(this.entries, chance)); if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { slotWidget.markInput(); } else if (recipeSlotRole == RecipeSlotRole.OUTPUT) { @@ -73,11 +37,8 @@ private void rebuildReiSlot() { } @Override - public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - context.getGraphics().pose().pushPose(); - context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); + public void drawRealSlot(ModularGuiContext context) { this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); - context.getGraphics().pose().popPose(); } @Override From 4b1f982c3b23f3d1a1759b936c5455518185df9a Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 13:23:32 +0300 Subject: [PATCH 12/84] Make recipe viewer handlers handle RecipeViewerSlotWidget creation & create a dummy recipe viewer slot widget impl --- .../emi/handler/EmiScreenHandler.java | 9 +++++++-- .../jei/handler/JeiScreenHandler.java | 7 +++++++ .../recipeviewer/RecipeViewerSlotWidget.java | 17 +---------------- .../handlers/RecipeViewerHandler.java | 17 +++++++++++++++++ .../rei/handler/REIScreenHandler.java | 7 +++++++ 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java b/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java index 1d3c9710..a2dde36e 100755 --- a/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java @@ -2,14 +2,14 @@ import brachy.modularui.api.IMuiScreen; import brachy.modularui.api.widget.IWidget; +import brachy.modularui.integration.emi.EmiRecipeViewerSlot; import brachy.modularui.integration.emi.EmiStackConverter; +import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.handlers.GhostIngredientSlot; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; import net.minecraft.client.gui.screens.Screen; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.material.Fluid; import dev.emi.emi.api.EmiApi; import dev.emi.emi.api.EmiDragDropHandler; @@ -140,4 +140,9 @@ public boolean isSearchFocused() { } return dragged; } + + @Override + public RecipeViewerSlotWidget createRecipeViewerSlot() { + return new EmiRecipeViewerSlot(); + } } diff --git a/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java b/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java index a23473d2..d8900f45 100755 --- a/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java @@ -4,8 +4,10 @@ import brachy.modularui.api.widget.IWidget; import brachy.modularui.core.mixins.jei.IngredientListOverlayAccessor; import brachy.modularui.integration.jei.GhostIngredientTarget; +import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; import brachy.modularui.integration.jei.ModularUIJeiProperties; +import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.handlers.GhostIngredientSlot; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; @@ -121,6 +123,11 @@ public boolean isSearchFocused(){ return currentIngredient; } + @Override + public RecipeViewerSlotWidget createRecipeViewerSlot() { + return new JeiRecipeViewerSlot(); + } + public static class ContainerScreen & IMuiScreen> extends JeiScreenHandler implements IGuiContainerHandler { diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java index da02b54c..dfec1b3b 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java @@ -1,15 +1,11 @@ package brachy.modularui.integration.recipeviewer; -import brachy.modularui.ModularUI; import brachy.modularui.api.widget.Interactable; import brachy.modularui.drawable.GuiTextures; -import brachy.modularui.integration.emi.EmiRecipeViewerSlot; -import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.recipeviewer.entry.EntryList; import brachy.modularui.integration.recipeviewer.entry.fluid.FluidStackList; import brachy.modularui.integration.recipeviewer.entry.item.ItemStackList; import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; -import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.theme.WidgetThemeEntry; import brachy.modularui.widget.Widget; @@ -70,17 +66,6 @@ public final void draw(ModularGuiContext context, WidgetThemeEntry widgetThem } public static RecipeViewerSlotWidget create() { - if (!ModularUI.Mods.isRecipeViewerLoaded()) { - throw new IllegalStateException("Cannot create recipe viewer slot without a recipe viewer mod loaded."); - } - - if (ModularUI.Mods.EMI.isLoaded()) { - return new EmiRecipeViewerSlot(); - } else if (ModularUI.Mods.REI.isLoaded()) { - return new ReiRecipeViewerSlot(); - } else if (ModularUI.Mods.JEI.isLoaded()) { - return new JeiRecipeViewerSlot(); - } - throw new UnsupportedOperationException("Cannot create recipe viewer slot without EMI, REI, or JEI being loaded."); + return RecipeViewerHandler.getCurrent().createRecipeViewerSlot(); } } diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java b/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java index 290a9f02..00ec34b8 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java @@ -3,9 +3,12 @@ import brachy.modularui.ModularUI; import brachy.modularui.integration.emi.handler.EmiScreenHandler; import brachy.modularui.integration.jei.handler.JeiScreenHandler; +import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.rei.handler.REIScreenHandler; import brachy.modularui.screen.ScreenWrapper; +import brachy.modularui.screen.viewport.ModularGuiContext; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,6 +43,8 @@ public static RecipeViewerHandler getCurrent() { public abstract @Nullable Object getCurrentlyDragged(); + public abstract RecipeViewerSlotWidget createRecipeViewerSlot(); + public boolean isHoveringOver(GhostIngredientSlot slot) { Object currentlyDragged = getCurrentlyDragged(); if (currentlyDragged == null) { @@ -70,5 +75,17 @@ public boolean isSearchFocused() { public @Nullable Object getCurrentlyDragged() { return null; } + + @Override + public RecipeViewerSlotWidget createRecipeViewerSlot() { + class DummyRecipeViewerSlot extends RecipeViewerSlotWidget { + @Override + protected void rebuildRealSlot() {} + + @Override + protected void drawRealSlot(ModularGuiContext context) {} + } + return new DummyRecipeViewerSlot(); + } }; } diff --git a/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java b/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java index 5264bedf..59b15d75 100755 --- a/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java @@ -2,9 +2,11 @@ import brachy.modularui.api.IMuiScreen; import brachy.modularui.api.widget.IWidget; +import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; import brachy.modularui.integration.rei.REIStackConverter; +import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.utils.Rectangle; import net.minecraft.client.gui.screens.Screen; @@ -146,4 +148,9 @@ public boolean isSearchFocused(){ if (currentIngredient == null) return null; return currentIngredient.get().getValue(); } + + @Override + public RecipeViewerSlotWidget createRecipeViewerSlot() { + return new ReiRecipeViewerSlot(); + } } From e32f253e0de0fd90472041e907025d51de88d1f5 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 13:24:00 +0300 Subject: [PATCH 13/84] don't hardcode EMI dragged item conversion to items and fluids only --- .../integration/emi/handler/EmiScreenHandler.java | 9 ++++----- .../modularui/integration/rei/ModularUIREIPlugin.java | 1 - .../integration/rei/handler/REIScreenHandler.java | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java b/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java index a2dde36e..ebc6c296 100755 --- a/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java @@ -131,11 +131,10 @@ public boolean isSearchFocused() { if (stack.isEmpty()) { continue; } - if (stack.getKeyOfType(Item.class) != null) { - dragged.add(EmiStackConverter.ITEM.convertFrom(stack)); - } - if (stack.getKeyOfType(Fluid.class) != null) { - dragged.add(EmiStackConverter.FLUID.convertFrom(stack)); + for (var entry : EmiStackConverter.CONVERTERS.entrySet()) { + if (stack.getKeyOfType(entry.getKey()) != null) { + dragged.add(entry.getValue().convertFrom(stack)); + } } } return dragged; diff --git a/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java b/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java index 08c81c20..2bc698e4 100644 --- a/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java +++ b/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java @@ -23,5 +23,4 @@ public void registerScreens(ScreenRegistry registry) { REIScreenHandler.register(ScreenWrapper.class, registry); REIScreenHandler.register(ContainerScreenWrapper.class, registry); } - } diff --git a/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java b/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java index 59b15d75..6ece2009 100755 --- a/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java @@ -137,7 +137,7 @@ public void setSearchFocused(boolean focused) { } @Override - public boolean isSearchFocused(){ + public boolean isSearchFocused() { TextField searchField = REIRuntime.getInstance().getSearchTextField(); if (searchField != null) return searchField.isFocused(); return false; From 15736cd34436d76aca9edffb772aad7ec619891a Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:08:59 +0300 Subject: [PATCH 14/84] all the concrete RecipeViewerSlotWidget impls are internal --- .../modularui/integration/emi/EmiRecipeViewerSlot.java | 3 +-- .../modularui/integration/jei/JeiRecipeViewerSlot.java | 9 +++------ .../modularui/integration/rei/ReiRecipeViewerSlot.java | 3 +-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index 32ed2302..7aa3e92a 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -18,10 +18,9 @@ import java.util.function.UnaryOperator; -@ApiStatus.Experimental +@ApiStatus.Internal public class EmiRecipeViewerSlot extends RecipeViewerSlotWidget { - @ApiStatus.Internal @Getter private SlotWidget slotWidget; public EmiRecipeViewerSlot() { diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 941f96db..90b04bc7 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -23,14 +23,11 @@ import java.util.Optional; -@ApiStatus.Experimental -public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget implements ISlottedRecipeWidget { +@ApiStatus.Internal +public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget> implements ISlottedRecipeWidget { - @ApiStatus.Internal - @Getter @Setter private @UnknownNullability IRecipeSlotDrawable slotWidget; - @ApiStatus.Internal + @Getter private @UnknownNullability IRecipeSlotDrawable slotWidget; @Setter private IFocusGroup focuses; - @ApiStatus.Internal @Setter private ICycler cycler; public JeiRecipeViewerSlot() { diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 5d93dec5..3152c2e0 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -10,10 +10,9 @@ import me.shedaniel.rei.api.client.gui.widgets.Widgets; import org.jetbrains.annotations.ApiStatus; -@ApiStatus.Experimental +@ApiStatus.Internal public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget { - @ApiStatus.Internal @Getter private Slot slotWidget; public ReiRecipeViewerSlot() { From b905b373246130dbe6884734918a6e3b2124f8e8 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:10:47 +0300 Subject: [PATCH 15/84] Instead of creating a new slot, make JeiRecipeViewerSlot overwrite most of the actual JEI slot's values with new ones. this is done because JEI keeps track of all recipe slots & it's easier to replace existing slots' data than it is to replace the slots themselves. --- .../core/mixins/jei/RecipeLayoutAccessor.java | 14 +++ .../jei/RecipeLayoutBuilderAccessor.java | 13 ++ .../core/mixins/jei/RecipeSlotAccessor.java | 36 ++++++ .../integration/jei/JeiRecipeViewerSlot.java | 117 ++++++++++++++---- .../jei/recipe/ModularUIRecipeCategory.java | 26 ++-- src/main/resources/modularui.mixins.json | 5 +- 6 files changed, 180 insertions(+), 31 deletions(-) create mode 100644 src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java create mode 100644 src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java create mode 100644 src/main/java/brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java new file mode 100644 index 00000000..4b9cea64 --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java @@ -0,0 +1,14 @@ +package brachy.modularui.core.mixins.jei; + +import mezz.jei.library.gui.ingredients.CycleTicker; +import mezz.jei.library.gui.recipes.RecipeLayout; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(RecipeLayout.class) +public interface RecipeLayoutAccessor { + + @Accessor("cycleTicker") + CycleTicker modularui$getCycleTicker(); + +} diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java new file mode 100644 index 00000000..89f3f963 --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java @@ -0,0 +1,13 @@ +package brachy.modularui.core.mixins.jei; + +import mezz.jei.api.runtime.IIngredientManager; +import mezz.jei.library.gui.recipes.layout.builder.RecipeLayoutBuilder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(RecipeLayoutBuilder.class) +public interface RecipeLayoutBuilderAccessor { + + @Accessor("ingredientManager") + IIngredientManager modularui$getIngredientManager(); +} diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java new file mode 100644 index 00000000..f0cae1d7 --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java @@ -0,0 +1,36 @@ +package brachy.modularui.core.mixins.jei; + +import mezz.jei.api.gui.ingredient.IRecipeSlotRichTooltipCallback; +import mezz.jei.api.ingredients.ITypedIngredient; +import mezz.jei.api.recipe.RecipeIngredientRole; +import mezz.jei.library.gui.ingredients.ICycler; +import mezz.jei.library.gui.ingredients.RecipeSlot; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; +import java.util.Optional; + +@Mixin(RecipeSlot.class) +public interface RecipeSlotAccessor { + + @Accessor("role") + @Mutable + void modularui$setRole(RecipeIngredientRole role); + + @Accessor("cycler") + @Mutable + void modularui$setCycler(ICycler cycler); + + @Accessor("tooltipCallbacks") + List modularui$getTooltipCallbacks(); + + @Accessor("allIngredients") + @Mutable + void modularui$setAllIngredients(List>> allIngredients); + + @Accessor("displayIngredients") + @Mutable + void modularui$setDisplayIngredients(List>> displayIngredients); +} diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 90b04bc7..3594492e 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -1,28 +1,41 @@ package brachy.modularui.integration.jei; +import brachy.modularui.core.mixins.jei.RecipeSlotAccessor; +import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; -import brachy.modularui.integration.recipeviewer.entry.EntryList; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenPosition; -import net.minecraftforge.fluids.FluidStack; +import net.minecraft.resources.ResourceLocation; import brachy.modularui.screen.viewport.ModularGuiContext; +import it.unimi.dsi.fastutil.ints.IntSet; import lombok.Getter; import lombok.Setter; +import mezz.jei.api.gui.builder.IRecipeSlotBuilder; import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; import mezz.jei.api.gui.inputs.RecipeSlotUnderMouse; import mezz.jei.api.gui.widgets.ISlottedRecipeWidget; +import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeIngredientRole; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.category.IRecipeCategory; import mezz.jei.api.runtime.IIngredientManager; import mezz.jei.library.gui.ingredients.ICycler; -import mezz.jei.library.gui.recipes.layout.builder.RecipeSlotBuilder; +import mezz.jei.library.gui.recipes.OutputSlotTooltipCallback; +import mezz.jei.library.ingredients.DisplayIngredientAcceptor; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.UnknownNullability; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; +import static brachy.modularui.integration.jei.ModularUIJeiPlugin.mapToJeiRole; + @ApiStatus.Internal public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget> implements ISlottedRecipeWidget { @@ -30,42 +43,89 @@ public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget recipeCategory; + @Setter private R recipe; + + @Setter private IIngredientManager ingredientManager; + public JeiRecipeViewerSlot() { super(); size(18, 18); } - // TODO make sure this doesn't break everything (for example: search). It shouldn't but I'm not 100% on that. + public void setSlotWidget(IRecipeSlotDrawable slotWidget) { + this.slotWidget = slotWidget; + rebuildRealSlot(); + } + + /** + * Instead of creating a new slot, the JEI implementation (this class) overwrites most of the {@linkplain mezz.jei.library.gui.ingredients.RecipeSlot JEI recipe slot's} values with new ones.
+ * It does this because JEI keeps track of all recipe slots & it's easier to replace existing slots' data than it is to replace the slots themselves. + */ @Override protected void rebuildRealSlot() { - IIngredientManager ingredientManager = ModularUIJeiPlugin.getRuntime().getIngredientManager(); - - // kinda meh solution. this isn't API, but I can't find a good way to do this within the API. - RecipeSlotBuilder builder = new RecipeSlotBuilder(ingredientManager, 0, ModularUIJeiPlugin.mapToJeiRole(this.recipeSlotRole)); - JeiRecipeViewerSlot.addTypedIngredients(this.entries, builder); - builder.setPosition(this.getArea().x, this.getArea().y); + if (slotWidget == null && ModularUIJeiPlugin.hasRuntime()) { + // it won't matter that this slot has wholly invalid data because we overwrite all of it anyway. + slotWidget = ModularUIJeiPlugin.getRuntime().getRecipeManager() + .createRecipeSlotDrawable(mapToJeiRole(this.recipeSlotRole), Collections.emptyList(), Collections.emptySet(), 0); + } + if (!(slotWidget instanceof RecipeSlotAccessor recipeSlot)) { + // null check and cast in one! + return; + } - if (this.entries.getType() == FluidStack.class) { - // special case fluid slots (this is why we can't have nice things.) - builder.setFluidRenderer(1, false, 18, 18); + // only update the slot's role if it's out of date + RecipeIngredientRole jeiRole = mapToJeiRole(this.recipeSlotRole); + if (slotWidget.getRole() != jeiRole) { + // add/remove the output slot tooltip callback depending on if this is now an output slot or not + if (this.recipeSlotRole == RecipeSlotRole.OUTPUT) { + addOutputSlotTooltipCallback(recipeSlot); + } else { + recipeSlot.modularui$getTooltipCallbacks().removeIf(callback -> callback instanceof OutputSlotTooltipCallback); + } + recipeSlot.modularui$setRole(jeiRole); } + slotWidget.setPosition(this.getArea().x, this.getArea().y); - slotWidget = builder.build(this.focuses, this.cycler).second(); + replaceSlotIngredients(recipeSlot); + recipeSlot.modularui$setCycler(this.cycler); } - private static void addTypedIngredients(EntryList entries, RecipeSlotBuilder builder) { - IIngredientManager ingredientManager = ModularUIJeiPlugin.getRuntime().getIngredientManager(); - var ingredientType = ingredientManager.getIngredientTypeChecked(entries.getType()); - if (ingredientType.isEmpty()) { - return; + @ApiStatus.Internal + public void configureJeiSlotBuilder(IRecipeSlotBuilder builder) { + builder.addIngredientsUnsafe(this.entries.getStacks()); + } + + // Mostly copied from RecipeSlotBuilder#build + private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { + final DisplayIngredientAcceptor ingredients = new DisplayIngredientAcceptor(this.ingredientManager); + ingredients.addIngredientsUnsafe(this.entries.getStacks()); + + List>> allIngredients = ingredients.getAllIngredients(); + + IntSet focusMatches = ingredients.getMatches(this.focuses, mapToJeiRole(this.recipeSlotRole)); + List>> focusedIngredients = null; + + if (!focusMatches.isEmpty()) { + focusedIngredients = new ArrayList<>(); + for (Integer i : focusMatches) { + if (i < allIngredients.size()) { + Optional> ingredient = allIngredients.get(i); + focusedIngredients.add(ingredient); + } + } } - builder.addIngredients(ingredientType.get(), entries.getStacks()); + + recipeSlot.modularui$setAllIngredients(allIngredients); + recipeSlot.modularui$setDisplayIngredients(focusedIngredients); } @Override public void drawRealSlot(ModularGuiContext context) { - this.slotWidget.draw(context.getGraphics()); + if (slotWidget != null) { + slotWidget.draw(context.getGraphics()); + } } /* @Override @@ -80,8 +140,8 @@ public Result onKeyPressed(int keyCode, int scanCode, int modifiers) { */ @Override public Optional getSlotUnderMouse(double mouseX, double mouseY) { - if (isHovering()) { - return Optional.of(new RecipeSlotUnderMouse(this.slotWidget, this.getPosition())); + if (isHovering() && slotWidget != null) { + return Optional.of(new RecipeSlotUnderMouse(slotWidget, this.getPosition())); } return Optional.empty(); } @@ -91,6 +151,17 @@ public ScreenPosition getPosition() { return new ScreenPosition(getArea().x, getArea().y); } + // disable JEI draw functionality @Override public void drawWidget(GuiGraphics guiGraphics, double mouseX, double mouseY) {} + + // copied from RecipeLayoutBuilder#addOutputSlotTooltipCallback + private void addOutputSlotTooltipCallback(RecipeSlotAccessor slot) { + ResourceLocation recipeName = recipeCategory.getRegistryName(recipe); + if (recipeName != null) { + RecipeType recipeType = recipeCategory.getRecipeType(); + OutputSlotTooltipCallback callback = new OutputSlotTooltipCallback(recipeName, recipeType); + slot.modularui$getTooltipCallbacks().add(callback); + } + } } diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index e3a52e18..82ca9ab9 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -3,10 +3,11 @@ import brachy.modularui.api.drawable.IRichTextBuilder; import brachy.modularui.api.widget.ITooltip; import brachy.modularui.api.widget.IWidget; +import brachy.modularui.core.mixins.jei.RecipeLayoutAccessor; +import brachy.modularui.core.mixins.jei.RecipeLayoutBuilderAccessor; import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; -import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; @@ -21,6 +22,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.gui.builder.IRecipeSlotBuilder; import mezz.jei.api.gui.builder.ITooltipBuilder; import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; import mezz.jei.api.gui.ingredient.IRecipeSlotsView; @@ -138,7 +140,11 @@ public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { @ApiStatus.OverrideOnly public IWidget transformWidget(IRecipeExtrasBuilder builder, IWidget widget) { - if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; + if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; + + if (builder instanceof RecipeLayoutAccessor accessor) { + recipeViewerSlot.setCycler(accessor.modularui$getCycleTicker()); + } String name = recipeViewerSlot.getName(); assert name != null; // the slots should always have a name assigned in createRecipeSlotForWidget() @@ -148,24 +154,30 @@ public IWidget transformWidget(IRecipeExtrasBuilder builder, IWidget widget) { recipeViewerSlot.setSlotWidget(slot); builder.addSlottedWidget(recipeViewerSlot, List.of(slot)); - if (recipeViewerSlot.recipeSlotRole() == RecipeSlotRole.OUTPUT) { - // recipeViewerSlot.getSlotWidget().recipeContext(this); - } - return recipeViewerSlot; } + @SuppressWarnings({ "rawtypes", "unchecked" }) @ApiStatus.OverrideOnly public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget widget, T recipe, IFocusGroup focuses, int index) { + // guard against JEMI issues + if (!(builder instanceof RecipeLayoutBuilderAccessor recipeLayoutBuilder)) return widget; if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; + recipeViewerSlot.setIngredientManager(recipeLayoutBuilder.modularui$getIngredientManager()); + recipeViewerSlot.setRecipeCategory(this); + recipeViewerSlot.setRecipe(recipe); recipeViewerSlot.setFocuses(focuses); if (recipeViewerSlot.getName() == null) { recipeViewerSlot.name("jei_slot_" + index); } - builder.addSlot(ModularUIJeiPlugin.mapToJeiRole(recipeViewerSlot.recipeSlotRole())) + IRecipeSlotBuilder slotBuilder = builder.addSlot(ModularUIJeiPlugin.mapToJeiRole(recipeViewerSlot.recipeSlotRole())) .setSlotName(recipeViewerSlot.getName()); + recipeViewerSlot.configureJeiSlotBuilder(slotBuilder); + + // always configure a fluid renderer (it's only used if a fluid is in the slot) + slotBuilder.setFluidRenderer(1, false, 16, 16); return recipeViewerSlot; } diff --git a/src/main/resources/modularui.mixins.json b/src/main/resources/modularui.mixins.json index dd171a14..f9692041 100755 --- a/src/main/resources/modularui.mixins.json +++ b/src/main/resources/modularui.mixins.json @@ -22,8 +22,11 @@ "common.ComponentSerializerMixin", "common.SlotAccessor", "common.TextColorMixin", + "jei.RecipeLayoutAccessor", + "jei.RecipeLayoutBuilderAccessor", "jei.RecipeLayoutBuilderMixin", - "jei.RecipeLayoutMixin" + "jei.RecipeLayoutMixin", + "jei.RecipeSlotAccessor" ], "injectors": { "defaultRequire": 1, From d151f490a15392ed88961b7b7c8e6cb236704c0d Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:57:08 +0300 Subject: [PATCH 16/84] rename the non-recipe-specific `getWidth`/`get(Display)Height` methods in ModularUIRecipeCategory and ModularUIREIDisplayCategory to `getMaxWidth` and `getMax(Display)Height` respectively --- .../jei/recipe/ModularUIRecipeCategory.java | 62 +++++++++++++------ .../recipe/ModularUIREIDisplayCategory.java | 21 ++++--- 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 82ca9ab9..6ce0b25b 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -24,6 +24,7 @@ import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; import mezz.jei.api.gui.builder.IRecipeSlotBuilder; import mezz.jei.api.gui.builder.ITooltipBuilder; +import mezz.jei.api.gui.drawable.IDrawable; import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; import mezz.jei.api.gui.ingredient.IRecipeSlotsView; import mezz.jei.api.gui.inputs.IJeiGuiEventListener; @@ -32,6 +33,7 @@ import mezz.jei.api.recipe.category.IRecipeCategory; import org.apache.commons.lang3.mutable.MutableInt; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import java.time.Duration; import java.util.List; @@ -72,7 +74,7 @@ private ModularScreen getModularScreen(T recipe) { /** * Calculates the size of the recipe if not already done.
- * This should be called in subclasses' {@link #setRecipe(IRecipeLayoutBuilder, Object, IFocusGroup) setRecipe} methods. + * This should be called in subclasses' {@link #createRecipeDisplay(IRecipeLayoutBuilder, Object, IFocusGroup) createRecipeDisplay} methods. * Otherwise, the size of ALL the recipes in the same category are calculated at once, which can make the game lag for a few seconds. */ protected void calculateSize(T recipe) { @@ -92,31 +94,28 @@ protected void calculateSize(T recipe) { /** * Return the maximum expected display width here.
- * You should also return a per-category display width that's at most this value in {@link #getWidth(Object)}. + * You should also return a per-category display width that's at most this value in {@link #getWidth(Object)} if the default value + * doesn't suit your needs. + * * @return The maximum expected display width */ - @ApiStatus.OverrideOnly - @Override - public abstract int getWidth(); - - public int getWidth(T recipe) { - calculateSize(recipe); - return this.displayWidth; - } + public abstract int getMaxWidth(); /** * Return the maximum expected display height here.
- * You should also return a per-category display height that's at most this value in {@link #getHeight(Object)}. + * You should also return a per-category display height that's at most this value in {@link #getHeight(Object)} if the default value + * doesn't suit your needs. + * * @return The maximum expected display height */ - @ApiStatus.OverrideOnly - @Override - public abstract int getHeight(); + public abstract int getMaxHeight(); - public int getHeight(T recipe) { - calculateSize(recipe); - return this.displayHeight; - } + /** + * Customize your recipe displays here. + *

+ * This is used by JEI for lookups to figure out what ingredients are inputs and outputs for a recipe. + */ + public abstract void createRecipeDisplay(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses); private ModularScreen createScreen(T recipe) { ResourceLocation id = this.recipeIdGetter.apply(recipe); @@ -187,6 +186,8 @@ public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuse ModularScreen screen = getModularScreen(recipe); MutableInt i = new MutableInt(0); screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, i.getAndIncrement())); + + this.createRecipeDisplay(builder, recipe, focuses); } @Override @@ -227,6 +228,31 @@ public void draw(T recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics graphic EmbedHandler.drawEmbed(screen, graphics, Minecraft.getInstance().getPartialTick()); } + @Override + public @Nullable IDrawable getIcon() { + return null; + } + + @Override + public final int getHeight() { + return getMaxHeight(); + } + + public int getHeight(T recipe) { + calculateSize(recipe); + return this.displayHeight; + } + + @Override + public final int getWidth() { + return getMaxWidth(); + } + + public int getWidth(T recipe) { + calculateSize(recipe); + return this.displayWidth; + } + public class ModularUIGuiEventListener implements IJeiGuiEventListener { private final T recipe; diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java index 4a120166..e438c993 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java @@ -26,15 +26,6 @@ public int getDisplayWidth(D display) { return display.getDisplayWidth(); } - /** - * Return the maximum expected display height here.
- * You should also return a per-category display height that's at most this value in {@link #getDisplayHeight(ModularUIREIDisplay)}. - * @return The maximum expected display height - */ - @ApiStatus.OverrideOnly - @Override - public abstract int getDisplayHeight(); - /** * Returns the display-dependent height. * @@ -44,4 +35,16 @@ public int getDisplayWidth(D display) { public int getDisplayHeight(D display) { return display.getDisplayHeight(); } + + /** + * Return the maximum expected display height here.
+ * You should also return a per-category display height that's at most this value in {@link #getDisplayHeight(ModularUIREIDisplay)}. + * @return The maximum expected display height + */ + public abstract int getMaxDisplayHeight(); + + @Override + public final int getDisplayHeight() { + return getMaxDisplayHeight(); + } } From cd3af618f07c52000000772f541a385dd397774a Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:58:02 +0300 Subject: [PATCH 17/84] Move the recipeviewer compatibility test stuff to its own class and add variants for JEI and REI too (so it isn't just EMI) --- .../integration/emi/ModularUIEmiPlugin.java | 4 +- .../integration/jei/ModularUIJeiPlugin.java | 36 ++- .../integration/rei/ModularUIREIPlugin.java | 19 ++ .../rei/recipe/ModularUIREIDisplay.java | 4 +- .../brachy/modularui/test/TestMachine.java | 78 +------ .../modularui/test/TestRecipeViewerGuis.java | 219 ++++++++++++++++++ 6 files changed, 275 insertions(+), 85 deletions(-) create mode 100644 src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java diff --git a/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java b/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java index 88e1726c..5df34a5c 100644 --- a/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java +++ b/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java @@ -5,7 +5,7 @@ import brachy.modularui.screen.ContainerScreenWrapper; import brachy.modularui.screen.ScreenWrapper; -import brachy.modularui.test.TestMachine; +import brachy.modularui.test.TestRecipeViewerGuis; import dev.emi.emi.api.EmiEntrypoint; import dev.emi.emi.api.EmiPlugin; @@ -20,7 +20,7 @@ public void register(EmiRegistry registry) { EmiScreenHandler.register(ContainerScreenWrapper.class, registry); if (ModularUI.isDev()) { - TestMachine.EMI.register(registry); + TestRecipeViewerGuis.EMI.register(registry); } } } diff --git a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java index aeaa9a06..ba879e9a 100644 --- a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java +++ b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java @@ -9,13 +9,17 @@ import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.resources.ResourceLocation; +import brachy.modularui.test.TestRecipeViewerGuis; + import lombok.Getter; import mezz.jei.api.IModPlugin; import mezz.jei.api.JeiPlugin; import mezz.jei.api.helpers.IJeiHelpers; import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.registration.IGuiHandlerRegistration; +import mezz.jei.api.registration.IRecipeCatalystRegistration; import mezz.jei.api.registration.IRecipeCategoryRegistration; +import mezz.jei.api.registration.IRecipeRegistration; import mezz.jei.api.registration.IRecipeTransferRegistration; import mezz.jei.api.runtime.IJeiRuntime; @@ -34,6 +38,15 @@ public static boolean hasRuntime() { return runtime != null; } + public static RecipeIngredientRole mapToJeiRole(RecipeSlotRole slotRole) { + return switch (slotRole) { + case INPUT -> RecipeIngredientRole.INPUT; + case OUTPUT -> RecipeIngredientRole.OUTPUT; + case CATALYST -> RecipeIngredientRole.CATALYST; + case RENDER_ONLY -> RecipeIngredientRole.RENDER_ONLY; + }; + } + @Override public ResourceLocation getPluginUid() { return ModularUI.id("jei_plugin"); @@ -47,6 +60,10 @@ public void onRuntimeAvailable(IJeiRuntime jeiRuntime) { @Override public void registerCategories(IRecipeCategoryRegistration registration) { jeiHelpers = registration.getJeiHelpers(); + + if (ModularUI.isDev()) { + TestRecipeViewerGuis.JEI.registerCategory(registration); + } } @Override @@ -64,12 +81,17 @@ public void registerRecipeTransferHandlers(IRecipeTransferRegistration registrat //JeiContainerHandler.register(ModularContainerMenu.class, registration); } - public static RecipeIngredientRole mapToJeiRole(RecipeSlotRole slotRole) { - return switch (slotRole) { - case INPUT -> RecipeIngredientRole.INPUT; - case OUTPUT -> RecipeIngredientRole.OUTPUT; - case CATALYST -> RecipeIngredientRole.CATALYST; - case RENDER_ONLY -> RecipeIngredientRole.RENDER_ONLY; - }; + @Override + public void registerRecipes(IRecipeRegistration registration) { + if (ModularUI.isDev()) { + TestRecipeViewerGuis.JEI.registerRecipes(registration); + } + } + + @Override + public void registerRecipeCatalysts(IRecipeCatalystRegistration registration) { + if (ModularUI.isDev()) { + TestRecipeViewerGuis.JEI.registerRecipeCatalyst(registration); + } } } diff --git a/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java b/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java index 2bc698e4..84569874 100644 --- a/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java +++ b/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java @@ -1,10 +1,15 @@ package brachy.modularui.integration.rei; +import brachy.modularui.ModularUI; import brachy.modularui.integration.rei.handler.REIScreenHandler; import brachy.modularui.screen.ContainerScreenWrapper; import brachy.modularui.screen.ScreenWrapper; +import brachy.modularui.test.TestRecipeViewerGuis; + import me.shedaniel.rei.api.client.plugins.REIClientPlugin; +import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; +import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; import me.shedaniel.rei.api.client.registry.screen.ExclusionZones; import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry; import me.shedaniel.rei.forge.REIPluginClient; @@ -23,4 +28,18 @@ public void registerScreens(ScreenRegistry registry) { REIScreenHandler.register(ScreenWrapper.class, registry); REIScreenHandler.register(ContainerScreenWrapper.class, registry); } + + @Override + public void registerCategories(CategoryRegistry registry) { + if (ModularUI.isDev()) { + TestRecipeViewerGuis.REI.registerCategory(registry); + } + } + + @Override + public void registerDisplays(DisplayRegistry registry) { + if (ModularUI.isDev()) { + TestRecipeViewerGuis.REI.registerDisplay(registry); + } + } } diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index fa037c54..446658a0 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -59,7 +59,7 @@ private static ModularScreen getModularScreen(ModularUIREIDisplay display) { private final ResourceLocation recipeId; private final Supplier recipeUI; - @Getter protected final CategoryIdentifier categoryId; + @Getter protected final CategoryIdentifier categoryIdentifier; private boolean sizeCalculated = false; private Rectangle bounds; @@ -68,7 +68,7 @@ private static ModularScreen getModularScreen(ModularUIREIDisplay display) { public ModularUIREIDisplay(ResourceLocation recipeId, Supplier recipeUI, CategoryIdentifier categoryId) { this.recipeId = recipeId; this.recipeUI = recipeUI; - this.categoryId = categoryId; + this.categoryIdentifier = categoryId; } /** diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index b8a12644..2458e929 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -10,14 +10,11 @@ import brachy.modularui.drawable.GuiTextures; import brachy.modularui.drawable.progress.ProgressDrawable; import brachy.modularui.factory.PosGuiData; -import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; -import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; import brachy.modularui.screen.UISettings; import brachy.modularui.utils.Color; -import brachy.modularui.value.DoubleValue; import brachy.modularui.value.StringValue; import brachy.modularui.value.sync.BooleanSyncValue; import brachy.modularui.value.sync.DoubleSyncValue; @@ -48,31 +45,17 @@ import net.minecraftforge.items.ItemHandlerHelper; import net.minecraftforge.items.ItemStackHandler; import net.minecraftforge.items.wrapper.CombinedInvWrapper; -import net.minecraftforge.items.wrapper.EmptyHandler; -import dev.emi.emi.api.EmiRegistry; -import dev.emi.emi.api.recipe.EmiRecipeCategory; -import dev.emi.emi.api.stack.EmiIngredient; -import dev.emi.emi.api.stack.EmiStack; -import lombok.Getter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; @ApiStatus.Experimental public class TestMachine { - private static final IItemHandler EMPTY_INFINITE_ITEM_HANDLER = new EmptyHandler() { - @Override - public int getSlots() { - return Integer.MAX_VALUE; // pls don't iterate UwU - } - }; - public static class BE extends AbstractBlockEntity implements IUIHolder { private final ItemStackHandler input = new ItemStackHandler(4); @@ -179,12 +162,12 @@ public double getProgress() { public static class Recipe { - private final ResourceLocation resloc; - private final List in = new ArrayList<>(), out = new ArrayList<>(); + protected final ResourceLocation id; + protected final List in = new ArrayList<>(), out = new ArrayList<>(); private int ticks = 80; - public Recipe(String resloc) { - this.resloc = ModularUI.id(resloc); + public Recipe(String name) { + this.id = ModularUI.id(name); } public Recipe in(ItemStack stack) { @@ -311,58 +294,5 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler in, IIt .slot(new ModularSlot(out, i).canPut(false)) .recipeRole(RecipeSlotRole.OUTPUT)))); } - - public static IWidget buildViewerUI(Recipe recipe) { - var panel = new ModularPanel<>("recipe_viewer_recipe") - .coverChildren(60, 40) - .invisible(); - IWidget recipeUI = buildMachineUI(panel, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_INFINITE_ITEM_HANDLER, DoubleValue.simulateProgress(5000)); - recipeUI.visitTransformAllChildren(w -> { - if (w instanceof ItemSlot slot) { - List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.in : recipe.out; - int index = slot.getSlot().getSlotIndex(); - ItemStack item = index >= l.size() ? ItemStack.EMPTY : l.get(index); - return RecipeViewerSlotWidget.create() - .recipeSlotRole(slot.getRecipeRole()) - .value(item) - .copyResizerOf(w); - } - return w; - }); - return panel.child(recipeUI); - //return recipeUI; - } - } - - public static class EMI { - - public static final EmiRecipeCategory CATEGORY = new EmiRecipeCategory(ModularUI.id("machine"), EmiStack.of(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get())); - - public static void register(EmiRegistry registry) { - registry.addCategory(CATEGORY); - Recipes.list.stream() - .map(r -> new RecipeDisplay(() -> Recipes.buildViewerUI(r), r)) - .forEach(registry::addRecipe); - } - - public static class RecipeDisplay extends ModularUIEmiRecipe { - - private final Recipe recipe; - @Getter private final List inputs = new ArrayList<>(); - @Getter private final List outputs = new ArrayList<>(); - - public RecipeDisplay(Supplier widgetSupplier, Recipe recipe) { - super(recipe.resloc, widgetSupplier); - this.recipe = recipe; - recipe.in.stream().map(EmiStack::of).map(s -> (EmiIngredient) s).forEach(inputs::add); - recipe.out.stream().map(EmiStack::of).forEach(outputs::add); - calculateSize(); - } - - @Override - public EmiRecipeCategory getCategory() { - return CATEGORY; - } - } } } diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java new file mode 100644 index 00000000..0d3b2a57 --- /dev/null +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -0,0 +1,219 @@ +package brachy.modularui.test; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.wrapper.EmptyHandler; + +import brachy.modularui.ModularUI; +import brachy.modularui.api.widget.IWidget; +import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; +import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; +import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; +import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; +import brachy.modularui.integration.rei.recipe.ModularUIREIDisplayCategory; +import brachy.modularui.screen.ModularPanel; +import brachy.modularui.value.DoubleValue; +import brachy.modularui.widgets.slot.ItemSlot; + +import dev.emi.emi.api.EmiRegistry; +import dev.emi.emi.api.recipe.EmiRecipeCategory; +import dev.emi.emi.api.stack.EmiIngredient; +import dev.emi.emi.api.stack.EmiStack; +import lombok.Getter; +import me.shedaniel.rei.api.client.gui.Renderer; +import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; +import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.api.common.util.EntryIngredients; +import me.shedaniel.rei.api.common.util.EntryStacks; +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.registration.IRecipeCatalystRegistration; +import mezz.jei.api.registration.IRecipeCategoryRegistration; +import mezz.jei.api.registration.IRecipeRegistration; +import org.jetbrains.annotations.NotNullByDefault; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; + +public class TestRecipeViewerGuis { + + private static final IItemHandler EMPTY_INFINITE_ITEM_HANDLER = new EmptyHandler() { + @Override + public int getSlots() { + return Integer.MAX_VALUE; // pls don't iterate UwU + } + }; + public static final Component CATEGORY_TITLE = Component.translatable("recipe_category.modularui.machine"); + + public static IWidget buildViewerUI(TestMachine.Recipe recipe) { + var panel = new ModularPanel<>("recipe_viewer_recipe") + .coverChildren(60, 40) + .invisible(); + IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_INFINITE_ITEM_HANDLER, DoubleValue.simulateProgress(5000)); + recipeUI.visitTransformAllChildren(w -> { + if (w instanceof ItemSlot slot) { + List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.in : recipe.out; + int index = slot.getSlot().getSlotIndex(); + ItemStack item = index >= l.size() ? ItemStack.EMPTY : l.get(index); + return RecipeViewerSlotWidget.create() + .recipeSlotRole(slot.getRecipeRole()) + .value(item) + .copyResizerOf(w); + } + return w; + }); + return panel.child(recipeUI); + } + + public static class EMI { + + public static final EmiRecipeCategory CATEGORY = new EmiRecipeCategory(ModularUI.id("machine"), EmiStack.of(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get())); + + public static void register(EmiRegistry registry) { + registry.addCategory(CATEGORY); + TestMachine.Recipes.list.stream() + .map(r -> new RecipeDisplay(() -> buildViewerUI(r), r)) + .forEach(registry::addRecipe); + } + + public static class RecipeDisplay extends ModularUIEmiRecipe { + + private final TestMachine.Recipe recipe; + @Getter private final List inputs = new ArrayList<>(); + @Getter private final List outputs = new ArrayList<>(); + + public RecipeDisplay(Supplier widgetSupplier, TestMachine.Recipe recipe) { + super(recipe.id, widgetSupplier); + this.recipe = recipe; + recipe.in.stream().map(EmiStack::of).forEach(inputs::add); + recipe.out.stream().map(EmiStack::of).forEach(outputs::add); + + calculateSize(); + } + + @Override + public EmiRecipeCategory getCategory() { + return CATEGORY; + } + } + } + + public static class JEI { + + public static final RecipeType RECIPE_TYPE = new RecipeType<>(ModularUI.id("machine"), TestMachine.Recipe.class); + + public static void registerCategory(IRecipeCategoryRegistration registry) { + registry.addRecipeCategories(new RecipeCategory(TestRecipeViewerGuis::buildViewerUI, r -> r.id)); + } + + public static void registerRecipes(IRecipeRegistration registry) { + registry.addRecipes(RECIPE_TYPE, TestMachine.Recipes.list); + } + + public static void registerRecipeCatalyst(IRecipeCatalystRegistration registry) { + registry.addRecipeCatalyst(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get(), RECIPE_TYPE); + } + + @NotNullByDefault + public static class RecipeCategory extends ModularUIRecipeCategory { + + protected RecipeCategory(Function recipeUI, + Function recipeIdGetter) { + super(recipeUI, recipeIdGetter); + } + + @Override + public RecipeType getRecipeType() { + return RECIPE_TYPE; + } + + @Override + public Component getTitle() { + return CATEGORY_TITLE; + } + + @Override + public int getMaxWidth() { + return 112; + } + + @Override + public int getMaxHeight() { + return 108; + } + + @Override + public void createRecipeDisplay(IRecipeLayoutBuilder builder, TestMachine.Recipe recipe, IFocusGroup focuses) { + calculateSize(recipe); + } + } + } + + public static class REI { + + public static final CategoryIdentifier CATEGORY = CategoryIdentifier.of(ModularUI.id("machine")); + + public static void registerCategory(CategoryRegistry registry) { + registry.add(new RecipeCategory()); + registry.addWorkstations(CATEGORY, EntryStacks.of(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get())); + } + + public static void registerDisplay(DisplayRegistry registry) { + TestMachine.Recipes.list.stream() + .map(r -> new RecipeDisplay(() -> buildViewerUI(r), r)) + .forEach(registry::add); + } + + public static class RecipeDisplay extends ModularUIREIDisplay { + + private final TestMachine.Recipe recipe; + @Getter private final List inputEntries = new ArrayList<>(); + @Getter private final List outputEntries = new ArrayList<>(); + + public RecipeDisplay(Supplier recipeUI, TestMachine.Recipe recipe) { + super(recipe.id, recipeUI, CATEGORY); + this.recipe = recipe; + recipe.in.stream().map(EntryIngredients::of).forEach(inputEntries::add); + recipe.out.stream().map(EntryIngredients::of).forEach(outputEntries::add); + + calculateSize(); + } + } + + @NotNullByDefault + public static class RecipeCategory extends ModularUIREIDisplayCategory { + + protected RecipeCategory() { + super(); + } + + @Override + public CategoryIdentifier getCategoryIdentifier() { + return CATEGORY; + } + + @Override + public Component getTitle() { + return CATEGORY_TITLE; + } + + @Override + public Renderer getIcon() { + return EntryStacks.of(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get()); + } + + @Override + public int getMaxDisplayHeight() { + return 108; + } + } + } +} From 92f2b224df5a5e1012ee2d6bcf250bce91437b69 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:03:10 +0300 Subject: [PATCH 18/84] , remap = false --- .../brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java | 2 +- .../modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java | 2 +- .../brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java index 4b9cea64..eaa52a41 100644 --- a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutAccessor.java @@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -@Mixin(RecipeLayout.class) +@Mixin(value = RecipeLayout.class, remap = false) public interface RecipeLayoutAccessor { @Accessor("cycleTicker") diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java index 89f3f963..0c499b66 100644 --- a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderAccessor.java @@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -@Mixin(RecipeLayoutBuilder.class) +@Mixin(value = RecipeLayoutBuilder.class, remap = false) public interface RecipeLayoutBuilderAccessor { @Accessor("ingredientManager") diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java index f0cae1d7..07fd3b56 100644 --- a/src/main/java/brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeSlotAccessor.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.Optional; -@Mixin(RecipeSlot.class) +@Mixin(value = RecipeSlot.class, remap = false) public interface RecipeSlotAccessor { @Accessor("role") From 6eae77032707937cf92a1f9415f72a70b86f6b1d Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:16:49 +0300 Subject: [PATCH 19/84] aw, crud. `ModLoader.loadingStateValid` is FALSE when the class is initialized. why is that? no clue. Oh well. --- .../java/brachy/modularui/core/ModularUIMixinPlugin.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/brachy/modularui/core/ModularUIMixinPlugin.java b/src/main/java/brachy/modularui/core/ModularUIMixinPlugin.java index e11a759b..06222cbe 100644 --- a/src/main/java/brachy/modularui/core/ModularUIMixinPlugin.java +++ b/src/main/java/brachy/modularui/core/ModularUIMixinPlugin.java @@ -20,9 +20,9 @@ public class ModularUIMixinPlugin implements IMixinConfigPlugin { private static final String DEV_PACKAGE = "dev."; static { - MOD_COMPAT_MIXINS.put("roughlyenoughitems", "rei"); - MOD_COMPAT_MIXINS.put("emi", "emi"); - MOD_COMPAT_MIXINS.put("jei", "jei"); + MOD_COMPAT_MIXINS.put("roughlyenoughitems", "rei."); + MOD_COMPAT_MIXINS.put("emi", "emi."); + MOD_COMPAT_MIXINS.put("jei", "jei."); } @Override @@ -32,7 +32,7 @@ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { // this should never happen, but better safe than sorry return true; } - if (!FMLLoader.getLoadingModList().getErrors().isEmpty() || !ModLoader.isLoadingStateValid()) { + if (!FMLLoader.getLoadingModList().getErrors().isEmpty()) { // stop processing mixins if we have load errors to avoid getting bad crash reports in our issues return false; } From 3273315d850502f5b82ae5bbdcfc4091b75e2075 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:05:40 +0300 Subject: [PATCH 20/84] assign default values to gui size if the size is 0 because MUI initializes the screen/panel sizing after JEI wants to use them --- .../jei/ModularUIJeiProperties.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiProperties.java b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiProperties.java index f7623512..fad258ff 100644 --- a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiProperties.java +++ b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiProperties.java @@ -3,8 +3,11 @@ import brachy.modularui.api.IMuiScreen; import brachy.modularui.widget.sizer.Area; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; +import com.mojang.blaze3d.platform.Window; + import lombok.Getter; import mezz.jei.api.gui.handlers.IGuiProperties; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -16,6 +19,8 @@ */ public class ModularUIJeiProperties implements IGuiProperties { + private static final int DEFAULT_GUI_WIDTH = 176, DEFAULT_GUI_HEIGHT = 166; + @Getter private final Class screenClass; @Getter @@ -32,15 +37,20 @@ public class ModularUIJeiProperties implements IGuiProperties { private final int screenHeight; public ModularUIJeiProperties(IMuiScreen screen) { - Area mainArea = screen.screen().getMainPanel().getArea(); - Area screenArea = screen.screen().getScreenArea(); this.screenClass = screen.wrappedScreen().getClass(); - this.guiLeft = mainArea.x; - this.guiTop = mainArea.y; - this.guiXSize = mainArea.width; - this.guiYSize = mainArea.height; - this.screenWidth = screenArea.width; - this.screenHeight = screenArea.height; + + // we have to assign default values to gui size if the size is 0 (e.g. during the instant it's created) + // because MUI initializes the screen/panel sizing data after JEI wants to use them + Window window = Minecraft.getInstance().getWindow(); + Area screenArea = screen.screen().getScreenArea(); + this.screenWidth = screenArea.width == 0 ? window.getGuiScaledWidth() : screenArea.width; + this.screenHeight = screenArea.height == 0 ? window.getGuiScaledHeight() : screenArea.height; + + Area mainArea = screen.screen().getMainPanel().getArea(); + this.guiXSize = mainArea.width == 0 ? DEFAULT_GUI_WIDTH : mainArea.width; + this.guiYSize = mainArea.height == 0 ? DEFAULT_GUI_HEIGHT : mainArea.height; + this.guiLeft = mainArea.width == 0 ? (this.screenWidth - DEFAULT_GUI_WIDTH) / 2 : mainArea.x; + this.guiTop = mainArea.height == 0 ? (this.screenHeight - DEFAULT_GUI_HEIGHT) / 2 : mainArea.y; } @Override From 046e09cf21c83dd9e7a2486b1b22501b9ec9e1db Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:37:43 +0300 Subject: [PATCH 21/84] Add a generic for the ingredient type to RecipeViewerSlotWidget --- .../integration/emi/EmiRecipeViewerSlot.java | 16 +++--- .../integration/emi/EmiStackConverter.java | 4 +- .../emi/handler/EmiScreenHandler.java | 5 +- .../integration/jei/JeiRecipeViewerSlot.java | 14 +++-- .../jei/handler/JeiScreenHandler.java | 5 +- .../jei/recipe/ModularUIRecipeCategory.java | 2 +- .../recipeviewer/RecipeViewerSlotWidget.java | 51 +++++++++++++------ .../handlers/IngredientProvider.java | 5 ++ .../handlers/RecipeViewerHandler.java | 13 +++-- .../integration/rei/REIStackConverter.java | 4 +- .../integration/rei/ReiRecipeViewerSlot.java | 9 ++-- .../rei/handler/REIScreenHandler.java | 5 +- .../modularui/test/TestRecipeViewerGuis.java | 7 +-- 13 files changed, 83 insertions(+), 57 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index 7aa3e92a..265b86ec 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -3,7 +3,6 @@ import brachy.modularui.drawable.ClientTooltipComponentIcon; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; -import brachy.modularui.integration.recipeviewer.entry.EntryList; import brachy.modularui.screen.viewport.ModularGuiContext; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; @@ -16,15 +15,13 @@ import lombok.Getter; import org.jetbrains.annotations.ApiStatus; -import java.util.function.UnaryOperator; - @ApiStatus.Internal -public class EmiRecipeViewerSlot extends RecipeViewerSlotWidget { +public class EmiRecipeViewerSlot extends RecipeViewerSlotWidget> { @Getter private SlotWidget slotWidget; - public EmiRecipeViewerSlot() { - super(); + public EmiRecipeViewerSlot(Class ingredientClass) { + super(ingredientClass); this.slotWidget = new SlotWidget(EmiIngredient.of(Ingredient.EMPTY), 0, 0); size(18, 18); @@ -37,15 +34,14 @@ public EmiRecipeViewerSlot() { }); } - @SuppressWarnings("unchecked") @Override protected void rebuildRealSlot() { - if (this.entries.getType() == FluidStack.class) { + EmiIngredient ingredient = EmiStackConverter.convertToEmiEntry(this.entries, this.chance, this.renderMappingFunction); + if (this.ingredientClass == FluidStack.class) { // special case fluid slots - EmiIngredient ingredient = EmiStackConverter.FLUID.convertTo((EntryList) this.entries, chance, UnaryOperator.identity()); slotWidget = new TankWidget(ingredient, 0, 0, 18, 18, 1); } else { - slotWidget = new SlotWidget(EmiStackConverter.convertToEmiEntry(this.entries, chance), 0, 0); + slotWidget = new SlotWidget(ingredient, 0, 0); } slotWidget.drawBack(false); slotWidget.catalyst(recipeSlotRole == RecipeSlotRole.CATALYST); diff --git a/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java b/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java index 44055ec0..4ae056bc 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java @@ -126,10 +126,10 @@ public static Optional> getFor(Class clazz) { return Optional.ofNullable(getForNullable(clazz)); } - public static EmiIngredient convertToEmiEntry(EntryList entries, float chance) { + public static EmiIngredient convertToEmiEntry(EntryList entries, float chance, UnaryOperator renderMappingFunction) { Converter converter = getForNullable(entries.getType()); if (converter != null) { - return converter.convertTo(entries, chance, UnaryOperator.identity()); + return converter.convertTo(entries, chance, renderMappingFunction); } return EmiStack.EMPTY; } diff --git a/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java b/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java index ebc6c296..c2461406 100755 --- a/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/emi/handler/EmiScreenHandler.java @@ -4,7 +4,6 @@ import brachy.modularui.api.widget.IWidget; import brachy.modularui.integration.emi.EmiRecipeViewerSlot; import brachy.modularui.integration.emi.EmiStackConverter; -import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.handlers.GhostIngredientSlot; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; @@ -141,7 +140,7 @@ public boolean isSearchFocused() { } @Override - public RecipeViewerSlotWidget createRecipeViewerSlot() { - return new EmiRecipeViewerSlot(); + public EmiRecipeViewerSlot createRecipeViewerSlot(Class ingredientClass) { + return new EmiRecipeViewerSlot<>(ingredientClass); } } diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 3594492e..3f0c3c65 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -37,7 +37,7 @@ import static brachy.modularui.integration.jei.ModularUIJeiPlugin.mapToJeiRole; @ApiStatus.Internal -public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget> implements ISlottedRecipeWidget { +public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget> implements ISlottedRecipeWidget { @Getter private @UnknownNullability IRecipeSlotDrawable slotWidget; @Setter private IFocusGroup focuses; @@ -48,8 +48,8 @@ public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget ingredientClass) { + super(ingredientClass); size(18, 18); } @@ -94,13 +94,17 @@ protected void rebuildRealSlot() { @ApiStatus.Internal public void configureJeiSlotBuilder(IRecipeSlotBuilder builder) { - builder.addIngredientsUnsafe(this.entries.getStacks()); + builder.addIngredientsUnsafe(this.entries.getStacks().stream() + .map(this.renderMappingFunction) + .toList()); } // Mostly copied from RecipeSlotBuilder#build private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { final DisplayIngredientAcceptor ingredients = new DisplayIngredientAcceptor(this.ingredientManager); - ingredients.addIngredientsUnsafe(this.entries.getStacks()); + ingredients.addIngredientsUnsafe(this.entries.getStacks().stream() + .map(this.renderMappingFunction) + .toList()); List>> allIngredients = ingredients.getAllIngredients(); diff --git a/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java b/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java index d8900f45..a4992401 100755 --- a/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java @@ -7,7 +7,6 @@ import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; import brachy.modularui.integration.jei.ModularUIJeiProperties; -import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.handlers.GhostIngredientSlot; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; @@ -124,8 +123,8 @@ public boolean isSearchFocused(){ } @Override - public RecipeViewerSlotWidget createRecipeViewerSlot() { - return new JeiRecipeViewerSlot(); + public JeiRecipeViewerSlot createRecipeViewerSlot(Class ingredientClass) { + return new JeiRecipeViewerSlot<>(ingredientClass); } public static class ContainerScreen & IMuiScreen> diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 6ce0b25b..edcfacb8 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -139,7 +139,7 @@ public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { @ApiStatus.OverrideOnly public IWidget transformWidget(IRecipeExtrasBuilder builder, IWidget widget) { - if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; + if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; if (builder instanceof RecipeLayoutAccessor accessor) { recipeViewerSlot.setCycler(accessor.modularui$getCycleTicker()); diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java index dfec1b3b..d99418a9 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java @@ -5,6 +5,7 @@ import brachy.modularui.integration.recipeviewer.entry.EntryList; import brachy.modularui.integration.recipeviewer.entry.fluid.FluidStackList; import brachy.modularui.integration.recipeviewer.entry.item.ItemStackList; +import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.theme.WidgetThemeEntry; @@ -18,15 +19,22 @@ import lombok.experimental.Accessors; import org.jetbrains.annotations.ApiStatus; +import java.util.function.UnaryOperator; + +@Accessors(fluent = true) @ApiStatus.Experimental -public abstract class RecipeViewerSlotWidget> extends Widget implements Interactable { +public abstract class RecipeViewerSlotWidget> extends Widget implements Interactable { - @Accessors(fluent = true) + @Getter protected RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; + protected EntryList entries; @Getter @Setter protected float chance = 1f; + @Getter @Setter protected UnaryOperator renderMappingFunction = UnaryOperator.identity(); - @Accessors(fluent = true) - @Getter protected RecipeSlotRole recipeSlotRole = RecipeSlotRole.RENDER_ONLY; - protected EntryList entries; + protected final Class ingredientClass; + + public RecipeViewerSlotWidget(Class ingredientClass) { + this.ingredientClass = ingredientClass; + } public W recipeSlotRole(RecipeSlotRole recipeSlotRole) { this.recipeSlotRole = recipeSlotRole; @@ -34,10 +42,10 @@ public W recipeSlotRole(RecipeSlotRole recipeSlotRole) { return getThis(); } - public W value(EntryList entryList) { + public W value(EntryList entryList) { this.entries = entryList; rebuildRealSlot(); - if (this.entries.getType() == FluidStack.class) { + if (this.ingredientClass == FluidStack.class) { background(GuiTextures.SLOT_FLUID); } else { background(GuiTextures.SLOT_ITEM); // TODO other types @@ -45,12 +53,15 @@ public W value(EntryList entryList) { return getThis(); } - public W value(ItemStack stack) { - return value(ItemStackList.of(stack)); - } - - public W value(FluidStack stack) { - return value(FluidStackList.of(stack)); + @SuppressWarnings("unchecked") + public W value(I stack) { + if (stack.getClass() == ItemStack.class) { + return value((EntryList) ItemStackList.of((ItemStack) stack)); + } else if (stack.getClass() == FluidStack.class) { + return value((EntryList) FluidStackList.of((FluidStack) stack)); + } else { + throw new IllegalArgumentException("Cannot use value(stack) with non-standard stack types! Use value(entryList) instead."); + } } protected abstract void rebuildRealSlot(); @@ -65,7 +76,17 @@ public final void draw(ModularGuiContext context, WidgetThemeEntry widgetThem context.graphicsPose().popPose(); } - public static RecipeViewerSlotWidget create() { - return RecipeViewerHandler.getCurrent().createRecipeViewerSlot(); + public static RecipeViewerSlotWidget create(Class ingredientClass) { + return RecipeViewerHandler.getCurrent().createRecipeViewerSlot(ingredientClass); + } + + public static RecipeViewerSlotWidget createFrom(IngredientProvider slot) { + return RecipeViewerSlotWidget.create(slot.ingredientClass()) + .recipeSlotRole(slot.getRecipeRole()) + .value(slot.getIngredients()) + .chance(slot.chance()) + .renderMappingFunction(slot.renderMappingFunction()) + .copyResizerOf(slot) + .copyVisualsOf(slot); } } diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/IngredientProvider.java b/src/main/java/brachy/modularui/integration/recipeviewer/handlers/IngredientProvider.java index e5d496c3..bbc10d2a 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/IngredientProvider.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/handlers/IngredientProvider.java @@ -2,6 +2,7 @@ import brachy.modularui.api.widget.IWidget; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.entry.EntryList; import org.jetbrains.annotations.NotNull; @@ -46,4 +47,8 @@ default Object ingredientOverride() { default RecipeSlotRole getRecipeRole() { return RecipeSlotRole.RENDER_ONLY; } + + default RecipeViewerSlotWidget toRecipeViewerSlot() { + return RecipeViewerSlotWidget.createFrom(this); + } } diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java b/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java index 00ec34b8..9445cc42 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java @@ -43,7 +43,7 @@ public static RecipeViewerHandler getCurrent() { public abstract @Nullable Object getCurrentlyDragged(); - public abstract RecipeViewerSlotWidget createRecipeViewerSlot(); + public abstract RecipeViewerSlotWidget createRecipeViewerSlot(Class ingredientClass); public boolean isHoveringOver(GhostIngredientSlot slot) { Object currentlyDragged = getCurrentlyDragged(); @@ -77,15 +77,20 @@ public boolean isSearchFocused() { } @Override - public RecipeViewerSlotWidget createRecipeViewerSlot() { - class DummyRecipeViewerSlot extends RecipeViewerSlotWidget { + public RecipeViewerSlotWidget createRecipeViewerSlot(Class ingredientClass) { + class DummyRecipeViewerSlot extends RecipeViewerSlotWidget { + + DummyRecipeViewerSlot(Class ingredientClass) { + super(ingredientClass); + } + @Override protected void rebuildRealSlot() {} @Override protected void drawRealSlot(ModularGuiContext context) {} } - return new DummyRecipeViewerSlot(); + return new DummyRecipeViewerSlot(ingredientClass); } }; } diff --git a/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java b/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java index d41aa16c..95aee887 100644 --- a/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java +++ b/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java @@ -120,10 +120,10 @@ public static Optional> getFor(Class clazz) { return Optional.ofNullable(getForNullable(clazz)); } - public static EntryIngredient convertToReiEntry(EntryList entries, float chance) { + public static EntryIngredient convertToReiEntry(EntryList entries, float chance, UnaryOperator renderMappingFunction) { REIStackConverter.Converter converter = REIStackConverter.getForNullable(entries.getType()); if (converter != null) { - return converter.convertTo(entries, chance, UnaryOperator.identity()); + return converter.convertTo(entries, chance, renderMappingFunction); } return EntryIngredient.empty(); } diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 3152c2e0..5342e200 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -11,12 +11,12 @@ import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal -public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget { +public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget> { @Getter private Slot slotWidget; - public ReiRecipeViewerSlot() { - super(); + public ReiRecipeViewerSlot(Class ingredientClass) { + super(ingredientClass); this.slotWidget = Widgets.createSlot(new Point()).disableBackground(); size(18, 18); @@ -25,7 +25,8 @@ public ReiRecipeViewerSlot() { @Override protected void rebuildRealSlot() { slotWidget = Widgets.createSlot(new Point()).disableBackground(); - slotWidget.entries(REIStackConverter.convertToReiEntry(this.entries, chance)); + slotWidget.entries(REIStackConverter.convertToReiEntry(this.entries, this.chance, this.renderMappingFunction)); + if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { slotWidget.markInput(); } else if (recipeSlotRole == RecipeSlotRole.OUTPUT) { diff --git a/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java b/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java index 6ece2009..9fd54548 100755 --- a/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/rei/handler/REIScreenHandler.java @@ -2,7 +2,6 @@ import brachy.modularui.api.IMuiScreen; import brachy.modularui.api.widget.IWidget; -import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.recipeviewer.handlers.RecipeViewerHandler; import brachy.modularui.integration.rei.REIStackConverter; @@ -150,7 +149,7 @@ public boolean isSearchFocused() { } @Override - public RecipeViewerSlotWidget createRecipeViewerSlot() { - return new ReiRecipeViewerSlot(); + public ReiRecipeViewerSlot createRecipeViewerSlot(Class ingredientClass) { + return new ReiRecipeViewerSlot<>(ingredientClass); } } diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index 0d3b2a57..f51d2059 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -11,7 +11,6 @@ import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; -import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; import brachy.modularui.integration.rei.recipe.ModularUIREIDisplayCategory; import brachy.modularui.screen.ModularPanel; @@ -63,10 +62,8 @@ public static IWidget buildViewerUI(TestMachine.Recipe recipe) { List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.in : recipe.out; int index = slot.getSlot().getSlotIndex(); ItemStack item = index >= l.size() ? ItemStack.EMPTY : l.get(index); - return RecipeViewerSlotWidget.create() - .recipeSlotRole(slot.getRecipeRole()) - .value(item) - .copyResizerOf(w); + return slot.toRecipeViewerSlot() + .value(item); } return w; }); From 53a3913c762ba3fc1e6cc20acfa98ed3e7f4c2e6 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:40:36 +0300 Subject: [PATCH 22/84] support holderset entry lists in EMI/REI stack converters --- .../integration/emi/EmiStackConverter.java | 20 +++++++++++++++---- .../integration/rei/REIStackConverter.java | 16 +++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java b/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java index 4ae056bc..37f9ad5c 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiStackConverter.java @@ -1,10 +1,8 @@ package brachy.modularui.integration.emi; import brachy.modularui.integration.recipeviewer.entry.EntryList; -import brachy.modularui.integration.recipeviewer.entry.fluid.FluidStackList; -import brachy.modularui.integration.recipeviewer.entry.fluid.FluidTagList; -import brachy.modularui.integration.recipeviewer.entry.item.ItemStackList; -import brachy.modularui.integration.recipeviewer.entry.item.ItemTagList; +import brachy.modularui.integration.recipeviewer.entry.fluid.*; +import brachy.modularui.integration.recipeviewer.entry.item.*; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.utils.math.MathUtils; @@ -67,6 +65,13 @@ public EmiIngredient convertTo(EntryList stack, float chance, UnaryOp .collect(Collectors.toList()), tagList.getEntries().get(0).amount()) .setChance(chance); + } else if (stack instanceof ItemHolderSetList holderSetList) { + return EmiIngredient.of(holderSetList.getEntries().stream() + .map(ItemHolderSetList.ItemHolderSetEntry::stacks) + .map(stream -> toEMIIngredient(stream.map(mapper))) + .collect(Collectors.toList()), + holderSetList.getEntries().get(0).amount()) + .setChance(chance); } return EmiStack.EMPTY; } @@ -106,6 +111,13 @@ public EmiIngredient convertTo(EntryList stack, float chance, UnaryO .collect(Collectors.toList()), tagList.getEntries().get(0).amount()) .setChance(chance); + } else if (stack instanceof FluidHolderSetList holderSetList) { + return EmiIngredient.of(holderSetList.getEntries().stream() + .map(FluidHolderSetList.FluidHolderSetEntry::stacks) + .map(stream -> toEMIIngredient(stream.map(mapper))) + .collect(Collectors.toList()), + holderSetList.getEntries().get(0).amount()) + .setChance(chance); } return EmiStack.EMPTY; } diff --git a/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java b/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java index 95aee887..470b2491 100644 --- a/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java +++ b/src/main/java/brachy/modularui/integration/rei/REIStackConverter.java @@ -1,10 +1,8 @@ package brachy.modularui.integration.rei; import brachy.modularui.integration.recipeviewer.entry.EntryList; -import brachy.modularui.integration.recipeviewer.entry.fluid.FluidStackList; -import brachy.modularui.integration.recipeviewer.entry.fluid.FluidTagList; -import brachy.modularui.integration.recipeviewer.entry.item.ItemStackList; -import brachy.modularui.integration.recipeviewer.entry.item.ItemTagList; +import brachy.modularui.integration.recipeviewer.entry.fluid.*; +import brachy.modularui.integration.recipeviewer.entry.item.*; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.utils.math.MathUtils; @@ -59,6 +57,11 @@ public EntryIngredient convertTo(EntryList stack, float chance, Unary .map(ItemTagList.ItemTagEntry::stacks) .flatMap(stream -> toREIIngredient(stream.map(mapper)).stream()) .collect(Collectors.toList())); + } else if (stack instanceof ItemHolderSetList holderSetList) { + return EntryIngredient.of(holderSetList.getEntries().stream() + .map(ItemHolderSetList.ItemHolderSetEntry::stacks) + .flatMap(stream -> toREIIngredient(stream.map(mapper)).stream()) + .collect(Collectors.toList())); } return EntryIngredient.empty(); } @@ -99,6 +102,11 @@ public EntryIngredient convertTo(EntryList stack, float chance, Unar .map(FluidTagList.FluidTagEntry::stacks) .flatMap(stream -> toREIIngredient(stream.map(mapper)).stream()) .collect(Collectors.toList())); + } else if (stack instanceof FluidHolderSetList holderSetList) { + return EntryIngredient.of(holderSetList.getEntries().stream() + .map(FluidHolderSetList.FluidHolderSetEntry::stacks) + .flatMap(stream -> toREIIngredient(stream.map(mapper)).stream()) + .collect(Collectors.toList())); } return EntryIngredient.empty(); } From d499535b61d8e76581b17c540efa2c33ce396bdf Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:41:13 +0300 Subject: [PATCH 23/84] add non-item slots as blanks to test recipe viewer display --- src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index f51d2059..ace0e2f1 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -11,6 +11,7 @@ import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; import brachy.modularui.integration.rei.recipe.ModularUIREIDisplayCategory; import brachy.modularui.screen.ModularPanel; @@ -64,6 +65,8 @@ public static IWidget buildViewerUI(TestMachine.Recipe recipe) { ItemStack item = index >= l.size() ? ItemStack.EMPTY : l.get(index); return slot.toRecipeViewerSlot() .value(item); + } else if (w instanceof IngredientProvider slot) { + return slot.toRecipeViewerSlot(); } return w; }); From b829ca6eec3d482f308b99c833e7d610cbae395b Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 18:01:53 +0300 Subject: [PATCH 24/84] don't try to fill JEI recipe slot if it doesn't exist yet or its `entries` list is empty OOps. --- .../integration/jei/JeiRecipeViewerSlot.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 3f0c3c65..387fbdbb 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -30,7 +30,6 @@ import org.jetbrains.annotations.UnknownNullability; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -60,16 +59,12 @@ public void setSlotWidget(IRecipeSlotDrawable slotWidget) { } /** - * Instead of creating a new slot, the JEI implementation (this class) overwrites most of the {@linkplain mezz.jei.library.gui.ingredients.RecipeSlot JEI recipe slot's} values with new ones.
+ * Instead of creating a new slot, the JEI implementation of {@code RecipeViewerSlotWidget} (this class) overwrites most of the {@linkplain mezz.jei.library.gui.ingredients.RecipeSlot JEI recipe slot's} values with new ones.
* It does this because JEI keeps track of all recipe slots & it's easier to replace existing slots' data than it is to replace the slots themselves. */ @Override protected void rebuildRealSlot() { - if (slotWidget == null && ModularUIJeiPlugin.hasRuntime()) { - // it won't matter that this slot has wholly invalid data because we overwrite all of it anyway. - slotWidget = ModularUIJeiPlugin.getRuntime().getRecipeManager() - .createRecipeSlotDrawable(mapToJeiRole(this.recipeSlotRole), Collections.emptyList(), Collections.emptySet(), 0); - } + // only assign all of these values once the slot has been created from JEI's side. if (!(slotWidget instanceof RecipeSlotAccessor recipeSlot)) { // null check and cast in one! return; @@ -94,13 +89,20 @@ protected void rebuildRealSlot() { @ApiStatus.Internal public void configureJeiSlotBuilder(IRecipeSlotBuilder builder) { - builder.addIngredientsUnsafe(this.entries.getStacks().stream() - .map(this.renderMappingFunction) - .toList()); + if (this.entries != null && !this.entries.isEmpty()) { + builder.addIngredientsUnsafe(this.entries.getStacks().stream() + .map(this.renderMappingFunction) + .toList()); + } } // Mostly copied from RecipeSlotBuilder#build private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { + if (this.entries == null || this.entries.isEmpty()) { + recipeSlot.modularui$setAllIngredients(List.of()); + return; + } + final DisplayIngredientAcceptor ingredients = new DisplayIngredientAcceptor(this.ingredientManager); ingredients.addIngredientsUnsafe(this.entries.getStacks().stream() .map(this.renderMappingFunction) From b4e7d8c67fedee8a619c6893920131e9a4647695 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:49:00 +0300 Subject: [PATCH 25/84] Add button to open recipe category from test machine ui --- .../brachy/modularui/test/TestMachine.java | 10 ++++- .../modularui/test/TestRecipeViewerGuis.java | 31 ++++++++++++++ .../java/brachy/modularui/widget/Widget.java | 41 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index 2458e929..c86e2b6e 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -6,6 +6,7 @@ import brachy.modularui.api.IUIHolder; import brachy.modularui.api.drawable.Text; import brachy.modularui.api.value.IDoubleValue; +import brachy.modularui.api.widget.IGuiAction; import brachy.modularui.api.widget.IWidget; import brachy.modularui.drawable.GuiTextures; import brachy.modularui.drawable.progress.ProgressDrawable; @@ -289,7 +290,14 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler in, IIt .child(new ProgressWidget() .value(progress) .size(20) - .texture(GuiTextures.PROGRESS_ARROW, ProgressDrawable.Direction.RIGHT)) + .texture(GuiTextures.PROGRESS_ARROW, ProgressDrawable.Direction.RIGHT) + .configure(w -> { + w.listenGuiAction((IGuiAction.MouseReleased) (ctx, button) -> { + if (!ctx.isMouseAbove(w)) return false; + TestRecipeViewerGuis.openTestRecipeViewerCategory(); + return true; + }); + })) .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() .slot(new ModularSlot(out, i).canPut(false)) .recipeRole(RecipeSlotRole.OUTPUT)))); diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index ace0e2f1..286af093 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -9,6 +9,7 @@ import brachy.modularui.ModularUI; import brachy.modularui.api.widget.IWidget; import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; +import brachy.modularui.integration.jei.ModularUIJeiPlugin; import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; @@ -18,6 +19,7 @@ import brachy.modularui.value.DoubleValue; import brachy.modularui.widgets.slot.ItemSlot; +import dev.emi.emi.api.EmiApi; import dev.emi.emi.api.EmiRegistry; import dev.emi.emi.api.recipe.EmiRecipeCategory; import dev.emi.emi.api.stack.EmiIngredient; @@ -26,6 +28,7 @@ import me.shedaniel.rei.api.client.gui.Renderer; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; +import me.shedaniel.rei.api.client.view.ViewSearchBuilder; import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.entry.EntryIngredient; import me.shedaniel.rei.api.common.util.EntryIngredients; @@ -37,6 +40,7 @@ import mezz.jei.api.registration.IRecipeCategoryRegistration; import mezz.jei.api.registration.IRecipeRegistration; import org.jetbrains.annotations.NotNullByDefault; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -73,10 +77,24 @@ public static IWidget buildViewerUI(TestMachine.Recipe recipe) { return panel.child(recipeUI); } + public static void openTestRecipeViewerCategory() { + if (ModularUI.Mods.EMI.isLoaded()) { + EMI.openRecipeCategory(); + } else if (ModularUI.Mods.REI.isLoaded()) { + REI.openRecipeCategory(); + } else if (ModularUI.Mods.JEI.isLoaded()) { + JEI.openRecipeCategory(); + } + } + public static class EMI { public static final EmiRecipeCategory CATEGORY = new EmiRecipeCategory(ModularUI.id("machine"), EmiStack.of(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get())); + public static void openRecipeCategory() { + EmiApi.displayRecipeCategory(CATEGORY); + } + public static void register(EmiRegistry registry) { registry.addCategory(CATEGORY); TestMachine.Recipes.list.stream() @@ -110,6 +128,10 @@ public static class JEI { public static final RecipeType RECIPE_TYPE = new RecipeType<>(ModularUI.id("machine"), TestMachine.Recipe.class); + public static void openRecipeCategory() { + ModularUIJeiPlugin.getRuntime().getRecipesGui().showTypes(List.of(RECIPE_TYPE)); + } + public static void registerCategory(IRecipeCategoryRegistration registry) { registry.addRecipeCategories(new RecipeCategory(TestRecipeViewerGuis::buildViewerUI, r -> r.id)); } @@ -154,6 +176,11 @@ public int getMaxHeight() { public void createRecipeDisplay(IRecipeLayoutBuilder builder, TestMachine.Recipe recipe, IFocusGroup focuses) { calculateSize(recipe); } + + @Override + public @Nullable ResourceLocation getRegistryName(TestMachine.Recipe recipe) { + return recipe.id; + } } } @@ -161,6 +188,10 @@ public static class REI { public static final CategoryIdentifier CATEGORY = CategoryIdentifier.of(ModularUI.id("machine")); + public static void openRecipeCategory() { + ViewSearchBuilder.builder().addCategory(CATEGORY).open(); + } + public static void registerCategory(CategoryRegistry registry) { registry.add(new RecipeCategory()); registry.addWorkstations(CATEGORY, EntryStacks.of(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get())); diff --git a/src/main/java/brachy/modularui/widget/Widget.java b/src/main/java/brachy/modularui/widget/Widget.java index e15fbc20..539c8f18 100644 --- a/src/main/java/brachy/modularui/widget/Widget.java +++ b/src/main/java/brachy/modularui/widget/Widget.java @@ -886,6 +886,47 @@ public W name(String name) { return getThis(); } + /** + * This can be used to further configure widgets in ways not directly allowed by the builder functions. + *

+ * For example, adding a click action to a progress widget changes from this: + * {@snippet lang="java": + * ProgressWidget progressWidget = new ProgressWidget() + * .value() + * .texture(); + * progressWidget.listenGuiAction((IGuiAction.MousePressed) (ctx, button) -> { + * if (!ctx.isMouseAbove(progressWidget)) return false; + * //... + * return true; + * }); + * parentWidget.child(progressWidget) + * .child() + * //... + * } + * to this: + * {@snippet lang="java": + * parentWidget.child(new ProgressWidget() + * .value() + * .texture() + * .configure(w -> { + * w.listenGuiAction((IGuiAction.MousePressed) (ctx, button) -> { + * if (!ctx.isMouseAbove(w)) return false; + * //... + * return true; + * }); + * })) + * .child() + * //... + * } + * + * @param configurator the configuration function + * @return this + */ + public W configure(Consumer configurator) { + configurator.accept(getThis()); + return getThis(); + } + /** * Returns this widget with proper generic type. * From 4c1a6a259f1ed59d39f209372e0ac2568bdc381b Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:52:32 +0300 Subject: [PATCH 26/84] Add mouse enter/leave/start hover/end hover gui actions & use them for toggling the debug overlay when hovering recipe preview embeds --- .../modularui/api/widget/IGuiAction.java | 24 ++++++++ .../emi/recipe/ModularUIEmiRecipe.java | 5 +- .../jei/recipe/ModularUIRecipeCategory.java | 3 + .../util/RecipeDebugDecoratorUtil.java | 42 +++++++++++++ .../rei/recipe/ModularUIREIDisplay.java | 5 +- .../java/brachy/modularui/widget/Widget.java | 61 +++++++++++++++++++ 6 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java diff --git a/src/main/java/brachy/modularui/api/widget/IGuiAction.java b/src/main/java/brachy/modularui/api/widget/IGuiAction.java index 5686473c..64acc768 100644 --- a/src/main/java/brachy/modularui/api/widget/IGuiAction.java +++ b/src/main/java/brachy/modularui/api/widget/IGuiAction.java @@ -49,4 +49,28 @@ interface MouseDrag extends IGuiAction { boolean drag(GuiContext context, int button, double dragX, double dragY); } + + @FunctionalInterface + interface MouseEnterArea extends IGuiAction { + + void enter(GuiContext context); + } + + @FunctionalInterface + interface MouseLeaveArea extends IGuiAction { + + void leave(GuiContext context, int timeBelowMouse); + } + + @FunctionalInterface + interface MouseStartHover extends IGuiAction { + + void startHover(GuiContext context); + } + + @FunctionalInterface + interface MouseEndHover extends IGuiAction { + + void endHover(GuiContext context, int timeHovered); + } } diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index e404a455..28875f46 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -5,6 +5,7 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.emi.EmiRecipeViewerSlot; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; @@ -128,6 +129,8 @@ public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { } ModularScreen screen = ModularScreen.createEmbed(owner, panel); screen.getContext().getUISettings().drawTooltipExternally(true); + + RecipeDebugDecoratorUtil.addRecipeDebugOverlays(screen); return screen; } @@ -139,7 +142,7 @@ public ModularPanel transform(ModularPanel panel) { } public IWidget transformWidget(IWidget widget, Iterator in, Iterator out) { - if (!(widget instanceof EmiRecipeViewerSlot recipeViewerSlot)) return widget; + if (!(widget instanceof EmiRecipeViewerSlot recipeViewerSlot)) return widget; if (recipeViewerSlot.recipeSlotRole() == RecipeSlotRole.OUTPUT) { recipeViewerSlot.getSlotWidget().recipeContext(this); diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index edcfacb8..700232e4 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -8,6 +8,7 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; +import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; @@ -134,6 +135,8 @@ public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { } ModularScreen screen = ModularScreen.createEmbed(owner, panel); screen.getContext().getUISettings().drawTooltipExternally(true); + + RecipeDebugDecoratorUtil.addRecipeDebugOverlays(screen); return screen; } diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java new file mode 100644 index 00000000..b3207b25 --- /dev/null +++ b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java @@ -0,0 +1,42 @@ +package brachy.modularui.integration.recipeviewer.util; + +import net.minecraft.client.Minecraft; + +import brachy.modularui.ModularUIConfig; +import brachy.modularui.overlay.DebugOverlay; +import brachy.modularui.overlay.OverlayStack; +import brachy.modularui.screen.ModularScreen; + +import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.mutable.MutableObject; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +@UtilityClass +public class RecipeDebugDecoratorUtil { + + public static void addRecipeDebugOverlays(ModularScreen screen) { + if (!ModularUIConfig.Dev.debugUI()) { + return; + } + + MutableObject debugOverlay = new MutableObject<>(); + Runnable close = () -> { + ModularScreen overlay = debugOverlay.getValue(); + if (overlay != null) { + OverlayStack.close(overlay); + } + }; + + screen.getMainPanel() + .onMouseEnterArea((ctx) -> { + ModularScreen overlay = new DebugOverlay(screen.getScreenWrapper()); + overlay.constructOverlay(Minecraft.getInstance().screen); + OverlayStack.open(overlay); + + debugOverlay.setValue(overlay); + }) + .onMouseLeaveArea((ctx, timeBelowMouse) -> close.run()) + .onCloseAction(close); + } +} diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index 446658a0..f875e82f 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -5,6 +5,7 @@ import brachy.modularui.api.widget.IWidget; import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -126,6 +127,8 @@ public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { } ModularScreen screen = ModularScreen.createEmbed(owner, panel); screen.getContext().getUISettings().drawTooltipExternally(true); + + RecipeDebugDecoratorUtil.addRecipeDebugOverlays(screen); return screen; } @@ -138,7 +141,7 @@ public ModularPanel transform(ModularPanel panel) { @ApiStatus.OverrideOnly public IWidget transformWidget(IWidget widget, Iterator in, Iterator out) { - if (!(widget instanceof ReiRecipeViewerSlot recipeViewerSlot)) return widget; + if (!(widget instanceof ReiRecipeViewerSlot recipeViewerSlot)) return widget; if (recipeViewerSlot.recipeSlotRole() == RecipeSlotRole.OUTPUT) { // recipeViewerSlot.getSlotWidget().recipeContext(this); diff --git a/src/main/java/brachy/modularui/widget/Widget.java b/src/main/java/brachy/modularui/widget/Widget.java index 539c8f18..569ddd31 100644 --- a/src/main/java/brachy/modularui/widget/Widget.java +++ b/src/main/java/brachy/modularui/widget/Widget.java @@ -159,6 +159,11 @@ public class Widget> extends AbstractWidget implements IPosi @Nullable private Consumer onUpdateListener; + private @Nullable IGuiAction.MouseEnterArea mouseEnterArea; + private @Nullable IGuiAction.MouseLeaveArea mouseLeaveArea; + private @Nullable IGuiAction.MouseStartHover mouseStartHover; + private @Nullable IGuiAction.MouseEndHover mouseEndHover; + public Widget() { resizer(new StandardResizer(this)); } @@ -645,6 +650,42 @@ public void onUpdate() { } } + @MustBeInvokedByOverriders + @Override + public void onMouseStartHover() { + super.onMouseStartHover(); + if (this.mouseStartHover != null) { + this.mouseStartHover.startHover(getContext()); + } + } + + @MustBeInvokedByOverriders + @Override + public void onMouseEndHover() { + if (this.mouseEndHover != null) { + this.mouseEndHover.endHover(getContext(), getTicksHovered()); + } + super.onMouseEndHover(); + } + + @MustBeInvokedByOverriders + @Override + public void onMouseEnterArea() { + super.onMouseEnterArea(); + if (this.mouseEnterArea != null) { + this.mouseEnterArea.enter(getContext()); + } + } + + @MustBeInvokedByOverriders + @Override + public void onMouseLeaveArea() { + if (this.mouseLeaveArea != null) { + this.mouseLeaveArea.leave(getContext(), getTicksBelowMouse()); + } + super.onMouseLeaveArea(); + } + /** * Registers a gui action this widget can listen to. Gui action listeners can listen to several mouse and keyboard * input events. @@ -719,6 +760,26 @@ public W setEnabledIf(Predicate condition) { return onUpdateListener(w -> setEnabled(condition.test(w)), true); } + public W onMouseEnterArea(IGuiAction.MouseEnterArea mouseEnterArea) { + this.mouseEnterArea = mouseEnterArea; + return getThis(); + } + + public W onMouseLeaveArea(IGuiAction.MouseLeaveArea mouseLeaveArea) { + this.mouseLeaveArea = mouseLeaveArea; + return getThis(); + } + + public W onMouseStartHover(IGuiAction.MouseStartHover mouseStartHover) { + this.mouseStartHover = mouseStartHover; + return getThis(); + } + + public W onMouseEndHover(IGuiAction.MouseEndHover mouseEndHover) { + this.mouseEndHover = mouseEndHover; + return getThis(); + } + // ---------------- // === Resizing === // ---------------- From 14621fa6182e6895fa93f5c2ce7cc969530fb157 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:52:56 +0300 Subject: [PATCH 27/84] formattiering --- .../jei/handler/JeiScreenHandler.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java b/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java index a4992401..356b4030 100755 --- a/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java +++ b/src/main/java/brachy/modularui/integration/jei/handler/JeiScreenHandler.java @@ -25,6 +25,7 @@ import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.registration.IGuiHandlerRegistration; import mezz.jei.api.runtime.IClickableIngredient; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -109,7 +110,7 @@ public void setSearchFocused(boolean focused) { } @Override - public boolean isSearchFocused(){ + public boolean isSearchFocused() { if (ModularUIJeiPlugin.getRuntime().getIngredientListOverlay() instanceof IngredientListOverlayAccessor accessor){ return accessor.getSearchField().isFocused(); } @@ -127,17 +128,14 @@ public boolean isSearchFocused(){ return new JeiRecipeViewerSlot<>(ingredientClass); } - public static class ContainerScreen & IMuiScreen> - extends JeiScreenHandler - implements IGuiContainerHandler { + public static class ContainerScreen & IMuiScreen> extends JeiScreenHandler implements IGuiContainerHandler { @SuppressWarnings("unchecked") - public static & IMuiScreen> ContainerScreen ofContainer(Class clazz) { + public static & IMuiScreen> ContainerScreen ofContainer(Class clazz) { return (ContainerScreen) CACHE.computeIfAbsent(clazz, clz -> new ContainerScreen<>((Class) clz)); } - private ContainerScreen(Class clazz) { + private ContainerScreen(Class clazz) { super(clazz); } @@ -148,7 +146,7 @@ public void register(IGuiHandlerRegistration registry) { } @Override - public List getGuiExtraAreas(T1 screen) { + public @NotNull List getGuiExtraAreas(T screen) { return screen.screen().getContext() .getRecipeViewerSettings().getAllExclusionAreas() .stream().map(Rectangle::asRect2i) @@ -156,7 +154,7 @@ public List getGuiExtraAreas(T1 screen) { } @Override - public Optional> getClickableIngredientUnderMouse(T1 screen, double mouseX, double mouseY) { + public @NotNull Optional> getClickableIngredientUnderMouse(T screen, double mouseX, double mouseY) { IWidget hovered = screen.screen().getContext().getTopHovered(); if (hovered instanceof IngredientProvider provider) { var override = provider.ingredientOverride(); From b273710a3b5ea414bda8b1002afee69371306ae4 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:53:07 +0300 Subject: [PATCH 28/84] saner JEI slot indices --- .../integration/jei/recipe/ModularUIRecipeCategory.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 700232e4..59a5b1b7 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -161,7 +161,7 @@ public IWidget transformWidget(IRecipeExtrasBuilder builder, IWidget widget) { @SuppressWarnings({ "rawtypes", "unchecked" }) @ApiStatus.OverrideOnly - public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget widget, T recipe, IFocusGroup focuses, int index) { + public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget widget, T recipe, IFocusGroup focuses, MutableInt index) { // guard against JEMI issues if (!(builder instanceof RecipeLayoutBuilderAccessor recipeLayoutBuilder)) return widget; if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; @@ -171,7 +171,7 @@ public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget w recipeViewerSlot.setRecipe(recipe); recipeViewerSlot.setFocuses(focuses); if (recipeViewerSlot.getName() == null) { - recipeViewerSlot.name("jei_slot_" + index); + recipeViewerSlot.name("jei_slot_" + index.getAndIncrement()); } IRecipeSlotBuilder slotBuilder = builder.addSlot(ModularUIJeiPlugin.mapToJeiRole(recipeViewerSlot.recipeSlotRole())) @@ -187,8 +187,8 @@ public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget w @Override public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { ModularScreen screen = getModularScreen(recipe); - MutableInt i = new MutableInt(0); - screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, i.getAndIncrement())); + MutableInt index = new MutableInt(0); + screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, index)); this.createRecipeDisplay(builder, recipe, focuses); } From 079b6add8e7a68e2b56d4f05d71cd6d18f1e6138 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:53:33 +0300 Subject: [PATCH 29/84] I love off-by-one errors --- .../integration/jei/JeiRecipeViewerSlot.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 387fbdbb..87cbb0b3 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -70,18 +70,16 @@ protected void rebuildRealSlot() { return; } - // only update the slot's role if it's out of date RecipeIngredientRole jeiRole = mapToJeiRole(this.recipeSlotRole); - if (slotWidget.getRole() != jeiRole) { - // add/remove the output slot tooltip callback depending on if this is now an output slot or not - if (this.recipeSlotRole == RecipeSlotRole.OUTPUT) { - addOutputSlotTooltipCallback(recipeSlot); - } else { - recipeSlot.modularui$getTooltipCallbacks().removeIf(callback -> callback instanceof OutputSlotTooltipCallback); - } - recipeSlot.modularui$setRole(jeiRole); + // add/remove the output slot tooltip callback depending on if this is now an output slot or not + if (this.recipeSlotRole == RecipeSlotRole.OUTPUT) { + addOutputSlotTooltipCallback(recipeSlot); + } else { + recipeSlot.modularui$getTooltipCallbacks().removeIf(callback -> callback instanceof OutputSlotTooltipCallback); } - slotWidget.setPosition(this.getArea().x, this.getArea().y); + recipeSlot.modularui$setRole(jeiRole); + // mmm I love off-by-one errors + slotWidget.setPosition(this.getArea().x + 1, this.getArea().y + 1); replaceSlotIngredients(recipeSlot); recipeSlot.modularui$setCycler(this.cycler); @@ -100,6 +98,7 @@ public void configureJeiSlotBuilder(IRecipeSlotBuilder builder) { private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { if (this.entries == null || this.entries.isEmpty()) { recipeSlot.modularui$setAllIngredients(List.of()); + recipeSlot.modularui$setDisplayIngredients(null); return; } From c9f11470ef6569b072685094d405e37b54809056 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:53:52 +0300 Subject: [PATCH 30/84] fix slot hover overlay being in the wrong spot (oops.) --- .../brachy/modularui/integration/jei/JeiRecipeViewerSlot.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 87cbb0b3..720ac46a 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -143,10 +143,12 @@ public Result onKeyPressed(int keyCode, int scanCode, int modifiers) { return this.slotWidget.keyPressed(keyCode, scanCode, modifiers) ? Result.SUCCESS : Result.ACCEPT; } */ + private static final ScreenPosition ZERO_POS = new ScreenPosition(0, 0); + @Override public Optional getSlotUnderMouse(double mouseX, double mouseY) { if (isHovering() && slotWidget != null) { - return Optional.of(new RecipeSlotUnderMouse(slotWidget, this.getPosition())); + return Optional.of(new RecipeSlotUnderMouse(slotWidget, ZERO_POS)); } return Optional.empty(); } From dcc5d95ff651e2be484d7428db412a6ed4aa618e Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:54:21 +0300 Subject: [PATCH 31/84] actually respect the boolean argument in `Widget#excludeAreaInRecipeViewer(bool)` --- src/main/java/brachy/modularui/widget/Widget.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/widget/Widget.java b/src/main/java/brachy/modularui/widget/Widget.java index 569ddd31..a65f812b 100644 --- a/src/main/java/brachy/modularui/widget/Widget.java +++ b/src/main/java/brachy/modularui/widget/Widget.java @@ -876,7 +876,11 @@ public W excludeAreaInRecipeViewer() { public W excludeAreaInRecipeViewer(boolean val) { this.excludeAreaInRecipeViewer = val; if (isValid()) { - getContext().getRecipeViewerSettings().addExclusionArea(this); + if (this.excludeAreaInRecipeViewer) { + getContext().getRecipeViewerSettings().addExclusionArea(this); + } else { + getContext().getRecipeViewerSettings().removeExclusionArea(this); + } } return getThis(); } From dc40e0ea116664e0a899d547fdc8edea4dd42968 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:41:27 +0300 Subject: [PATCH 32/84] Allow closing the panel manager if it hasn't opened yet IDK how exactly this came to happen but this is the easiest fix I can think of so --- src/main/java/brachy/modularui/screen/PanelManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/brachy/modularui/screen/PanelManager.java b/src/main/java/brachy/modularui/screen/PanelManager.java index 034c944d..35aee089 100644 --- a/src/main/java/brachy/modularui/screen/PanelManager.java +++ b/src/main/java/brachy/modularui/screen/PanelManager.java @@ -222,7 +222,7 @@ public void closeTopPanel() { * @return if the screen was open */ public boolean closeAll() { - if (this.state.isOpen) { + if (this.state.isOpen || this.state == State.INIT) { // any open panel will be set to closed, but will not actually be removed, so it can be reopened this.panels.forEach(this::finalizePanel); setState(State.CLOSED); @@ -238,7 +238,7 @@ public boolean closeAll() { * @return if this screen was open */ public boolean closePanelsAndScreen() { - if (this.state.isOpen) { + if (this.state.isOpen || this.state == State.INIT) { // create a list with non-main panels // looping directly over panels may cause CME List> subPanels = new ArrayList<>(); @@ -259,7 +259,7 @@ public boolean closePanelsAndScreen() { void closeScreen() { // only close the screen without closing the panels // this is useful when we expect the screen to reopen at some point and the sync managers are still available - if (this.state.isOpen) { + if (this.state.isOpen || this.state == State.INIT) { setState(State.CLOSED); this.screen.onClose(); } From 093126356a894ef2fa035bbd4495a1b4355b9114 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:41:43 +0300 Subject: [PATCH 33/84] always remove previous output slot tooltip callback --- .../modularui/integration/jei/JeiRecipeViewerSlot.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 720ac46a..83d210d4 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -70,14 +70,13 @@ protected void rebuildRealSlot() { return; } - RecipeIngredientRole jeiRole = mapToJeiRole(this.recipeSlotRole); - // add/remove the output slot tooltip callback depending on if this is now an output slot or not + // always remove the output slot tooltip callback (this is easier than checking if it already exists etc.) + recipeSlot.modularui$getTooltipCallbacks().removeIf(callback -> callback instanceof OutputSlotTooltipCallback); if (this.recipeSlotRole == RecipeSlotRole.OUTPUT) { + // (re)add the output slot tooltip callback if this is now an output slot addOutputSlotTooltipCallback(recipeSlot); - } else { - recipeSlot.modularui$getTooltipCallbacks().removeIf(callback -> callback instanceof OutputSlotTooltipCallback); } - recipeSlot.modularui$setRole(jeiRole); + recipeSlot.modularui$setRole(mapToJeiRole(this.recipeSlotRole)); // mmm I love off-by-one errors slotWidget.setPosition(this.getArea().x + 1, this.getArea().y + 1); From 39afebda6e9fcd9f9bf14a81eb2e7be550c041f4 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 22:34:46 +0300 Subject: [PATCH 34/84] always null check `RecipeViewerSlotWidget.entries` and only create the slot if the list is nonnull --- .../integration/jei/JeiRecipeViewerSlot.java | 11 +++-------- .../recipeviewer/RecipeViewerSlotWidget.java | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 83d210d4..2d60518b 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -55,7 +55,7 @@ public JeiRecipeViewerSlot(Class ingredientClass) { public void setSlotWidget(IRecipeSlotDrawable slotWidget) { this.slotWidget = slotWidget; - rebuildRealSlot(); + if (this.entries != null) rebuildRealSlot(); } /** @@ -86,21 +86,16 @@ protected void rebuildRealSlot() { @ApiStatus.Internal public void configureJeiSlotBuilder(IRecipeSlotBuilder builder) { - if (this.entries != null && !this.entries.isEmpty()) { + if (this.entries != null) { builder.addIngredientsUnsafe(this.entries.getStacks().stream() .map(this.renderMappingFunction) .toList()); } + builder.setPosition(this.getArea().x + 1, this.getArea().y + 1); } // Mostly copied from RecipeSlotBuilder#build private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { - if (this.entries == null || this.entries.isEmpty()) { - recipeSlot.modularui$setAllIngredients(List.of()); - recipeSlot.modularui$setDisplayIngredients(null); - return; - } - final DisplayIngredientAcceptor ingredients = new DisplayIngredientAcceptor(this.ingredientManager); ingredients.addIngredientsUnsafe(this.entries.getStacks().stream() .map(this.renderMappingFunction) diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java index d99418a9..59a8cc78 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java @@ -38,13 +38,13 @@ public RecipeViewerSlotWidget(Class ingredientClass) { public W recipeSlotRole(RecipeSlotRole recipeSlotRole) { this.recipeSlotRole = recipeSlotRole; - rebuildRealSlot(); + if (this.entries != null) rebuildRealSlot(); return getThis(); } public W value(EntryList entryList) { this.entries = entryList; - rebuildRealSlot(); + if (this.entries != null) rebuildRealSlot(); if (this.ingredientClass == FluidStack.class) { background(GuiTextures.SLOT_FLUID); } else { From b97d70303e36e3e6a2fd57172841dfc4a414b6c4 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sun, 2 Aug 2026 22:34:59 +0300 Subject: [PATCH 35/84] forgot to implement onRuntimeUnavailable --- .../brachy/modularui/integration/jei/ModularUIJeiPlugin.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java index ba879e9a..a19afb69 100644 --- a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java +++ b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiPlugin.java @@ -57,6 +57,11 @@ public void onRuntimeAvailable(IJeiRuntime jeiRuntime) { runtime = jeiRuntime; } + @Override + public void onRuntimeUnavailable() { + runtime = null; + } + @Override public void registerCategories(IRecipeCategoryRegistration registration) { jeiHelpers = registration.getJeiHelpers(); From 669909be382014e1439f54cf9255f6eac7cb19ca Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:12:19 +0300 Subject: [PATCH 36/84] remove drawRealSlot --- .../integration/emi/EmiRecipeViewerSlot.java | 7 ++++++- .../integration/jei/JeiRecipeViewerSlot.java | 14 +++++++++----- .../recipeviewer/RecipeViewerSlotWidget.java | 10 ---------- .../recipeviewer/handlers/RecipeViewerHandler.java | 5 ----- .../integration/rei/ReiRecipeViewerSlot.java | 4 +++- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index 265b86ec..91c10de0 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -9,6 +9,8 @@ import net.minecraft.world.item.crafting.Ingredient; import net.minecraftforge.fluids.FluidStack; +import brachy.modularui.theme.WidgetThemeEntry; + import dev.emi.emi.api.stack.EmiIngredient; import dev.emi.emi.api.widget.SlotWidget; import dev.emi.emi.api.widget.TankWidget; @@ -48,8 +50,11 @@ protected void rebuildRealSlot() { } @Override - public void drawRealSlot(ModularGuiContext context) { + public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + context.graphicsPose().pushPose(); + context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); + context.graphicsPose().popPose(); } @Override diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 2d60518b..6c19a922 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -10,6 +10,8 @@ import brachy.modularui.screen.viewport.ModularGuiContext; +import brachy.modularui.theme.WidgetThemeEntry; + import it.unimi.dsi.fastutil.ints.IntSet; import lombok.Getter; import lombok.Setter; @@ -19,7 +21,6 @@ import mezz.jei.api.gui.widgets.ISlottedRecipeWidget; import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.recipe.IFocusGroup; -import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.recipe.RecipeType; import mezz.jei.api.recipe.category.IRecipeCategory; import mezz.jei.api.runtime.IIngredientManager; @@ -121,10 +122,13 @@ private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { } @Override - public void drawRealSlot(ModularGuiContext context) { - if (slotWidget != null) { - slotWidget.draw(context.getGraphics()); - } + public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + if (this.slotWidget == null) return; + + context.graphicsPose().pushPose(); + context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); + this.slotWidget.draw(context.getGraphics()); + context.graphicsPose().popPose(); } /* @Override diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java index 59a8cc78..704fe1f4 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerSlotWidget.java @@ -66,16 +66,6 @@ public W value(I stack) { protected abstract void rebuildRealSlot(); - protected abstract void drawRealSlot(ModularGuiContext context); - - @Override - public final void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - context.graphicsPose().pushPose(); - context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); - drawRealSlot(context); - context.graphicsPose().popPose(); - } - public static RecipeViewerSlotWidget create(Class ingredientClass) { return RecipeViewerHandler.getCurrent().createRecipeViewerSlot(ingredientClass); } diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java b/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java index 9445cc42..d1d5d25b 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/handlers/RecipeViewerHandler.java @@ -7,8 +7,6 @@ import brachy.modularui.integration.rei.handler.REIScreenHandler; import brachy.modularui.screen.ScreenWrapper; -import brachy.modularui.screen.viewport.ModularGuiContext; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -86,9 +84,6 @@ class DummyRecipeViewerSlot extends RecipeViewerSlotWidget widgetTheme) { this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); } From 53390edf96c232ee15835e030f5e24a6d13cbb97 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:13:04 +0300 Subject: [PATCH 37/84] use the "actual" wrapped screen even tho it should always be the main MC screen --- .../integration/recipeviewer/util/RecipeDebugDecoratorUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java index b3207b25..60ee7a38 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java @@ -31,7 +31,7 @@ public static void addRecipeDebugOverlays(ModularScreen screen) { screen.getMainPanel() .onMouseEnterArea((ctx) -> { ModularScreen overlay = new DebugOverlay(screen.getScreenWrapper()); - overlay.constructOverlay(Minecraft.getInstance().screen); + overlay.constructOverlay(screen.getScreenWrapper().wrappedScreen()); OverlayStack.open(overlay); debugOverlay.setValue(overlay); From 22ef559f6f6720d44ba7b640d093b258c0da61b9 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:13:40 +0300 Subject: [PATCH 38/84] Fix Fucked up and Evil REI recipe preview offset --- .../integration/rei/ReiRecipeViewerSlot.java | 6 ++- .../rei/recipe/ModularUIREIDisplay.java | 28 ++++++-------- .../brachy/modularui/screen/EmbedHandler.java | 38 +++++++++++++------ 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 67777413..7c5903af 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -15,18 +15,20 @@ @ApiStatus.Internal public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget> { + private static final Point ONE = new Point(1, 1); + @Getter private Slot slotWidget; public ReiRecipeViewerSlot(Class ingredientClass) { super(ingredientClass); - this.slotWidget = Widgets.createSlot(new Point()).disableBackground(); + this.slotWidget = Widgets.createSlot(ONE).disableBackground(); size(18, 18); } @Override protected void rebuildRealSlot() { - slotWidget = Widgets.createSlot(new Point()).disableBackground(); + slotWidget = Widgets.createSlot(ONE).disableBackground(); slotWidget.entries(REIStackConverter.convertToReiEntry(this.entries, this.chance, this.renderMappingFunction)); if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index f875e82f..506451c9 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -25,7 +25,6 @@ import me.shedaniel.rei.api.client.gui.widgets.Tooltip; import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; import me.shedaniel.rei.api.client.gui.widgets.Widget; -import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds; import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.entry.EntryIngredient; @@ -63,7 +62,6 @@ private static ModularScreen getModularScreen(ModularUIREIDisplay display) { @Getter protected final CategoryIdentifier categoryIdentifier; private boolean sizeCalculated = false; - private Rectangle bounds; private int displayWidth, displayHeight; public ModularUIREIDisplay(ResourceLocation recipeId, Supplier recipeUI, CategoryIdentifier categoryId) { @@ -92,11 +90,6 @@ protected void calculateSize() { this.displayHeight = h; } - public Rectangle getBounds() { - calculateSize(); - return this.bounds; - } - public int getDisplayWidth() { calculateSize(); return this.displayWidth; @@ -157,27 +150,30 @@ public Optional getDisplayLocation() { public List createWidgets(Rectangle bounds) { List widgets = new ArrayList<>(); - widgets.add(new UIWrapperWidget(this)); + widgets.add(new UIWrapperWidget(this, bounds.x, bounds.y)); return widgets; } - public static class UIWrapperWidget extends WidgetWithBounds { + public static class UIWrapperWidget extends Widget { private final ModularUIREIDisplay display; + private final float offsetX, offsetY; - public UIWrapperWidget(ModularUIREIDisplay display) { + public UIWrapperWidget(ModularUIREIDisplay display, float offsetX, float offsetY) { this.display = display; - } - - @Override - public Rectangle getBounds() { - return this.display.getBounds(); + this.offsetX = offsetX; + this.offsetY = offsetY; } @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { ModularScreen screen = getModularScreen(this.display); - EmbedHandler.drawEmbed(screen, graphics, partialTick); + EmbedHandler.drawEmbed(screen, graphics, partialTick, () -> { + screen.getContext().pushMatrix(); + screen.getContext().translate(this.offsetX, this.offsetY); + }, () -> { + screen.getContext().popMatrix(); + }); } @Override diff --git a/src/main/java/brachy/modularui/screen/EmbedHandler.java b/src/main/java/brachy/modularui/screen/EmbedHandler.java index 69bb81fe..55d1c4b7 100644 --- a/src/main/java/brachy/modularui/screen/EmbedHandler.java +++ b/src/main/java/brachy/modularui/screen/EmbedHandler.java @@ -9,7 +9,8 @@ import net.minecraft.client.gui.screens.Screen; import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; + +import org.jetbrains.annotations.Nullable; import java.util.function.Predicate; @@ -24,22 +25,33 @@ public static int getEmbedHeight(ModularScreen screen) { } public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float partialTicks) { - drawEmbed(screen, graphics, partialTicks, r -> true); + drawEmbed(screen, graphics, partialTicks, null, null); } public static void drawEmbedNoVanillaElements(ModularScreen screen, GuiGraphics graphics, float partialTicks) { - drawEmbed(screen, graphics, partialTicks, r -> false); + drawEmbedNoVanillaElements(screen, graphics, partialTicks, null, null); + } + + public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float partialTicks, + @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { + drawEmbed(screen, graphics, partialTicks, r -> true, beforeDraw, afterDraw); + } + + public static void drawEmbedNoVanillaElements(ModularScreen screen, GuiGraphics graphics, float partialTicks, + @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { + drawEmbed(screen, graphics, partialTicks, r -> false, beforeDraw, afterDraw); } public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float partialTicks, Predicate vanillaElementFilter) { + drawEmbed(screen, graphics, partialTicks, vanillaElementFilter, null, null); + } + + public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float partialTicks, Predicate vanillaElementFilter, + @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { screen.getContext().reset(); - PoseStack pose = graphics.pose(); - var m = pose.last().pose(); - pose.pushPose(); - pose.setIdentity(); // reset all current transformations and only reapply them for the main panel - screen.getMainPanel().transform((p, stack) -> { - stack.multiply(m); - }); + graphics.pose().pushPose(); + + if (beforeDraw != null) beforeDraw.run(); var defContext = ClientScreenHandler.getDefaultContext(); int mx = defContext.getAbsMouseX(); @@ -49,14 +61,18 @@ public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float p if (vanillaElementFilter != null) { RenderSystem.disableDepthTest(); ClientScreenHandler.drawVanillaElements(graphics, screen.getScreenWrapper().wrappedScreen(), mx, my, partialTicks, vanillaElementFilter); + RenderSystem.enableDepthTest(); } screen.drawForeground(graphics); + if (afterDraw != null) afterDraw.run(); + RenderSystem.enableDepthTest(); Lighting.setupFor3DItems(); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); - pose.popPose(); + + graphics.pose().popPose(); } public record EmbedWrapper(ModularScreen screen) implements IMuiScreen { From 802e5b19887cc32ad53b0fbb0ce9412e17a2830e Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:14:16 +0300 Subject: [PATCH 39/84] don't store stuff in a (thankfully constant) vector only to unwrap it immediately --- .../screen/viewport/GuiViewportStack.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/brachy/modularui/screen/viewport/GuiViewportStack.java b/src/main/java/brachy/modularui/screen/viewport/GuiViewportStack.java index 7aa618c2..491c630c 100755 --- a/src/main/java/brachy/modularui/screen/viewport/GuiViewportStack.java +++ b/src/main/java/brachy/modularui/screen/viewport/GuiViewportStack.java @@ -21,8 +21,6 @@ */ public class GuiViewportStack implements IViewportStack { - private static final Vector3f sharedVec = new Vector3f(); - private final ObjectArrayList matrixPool = new ObjectArrayList<>(256); private final ObjectArrayList viewportStack = new ObjectArrayList<>(); private final List viewportAreas = new ArrayList<>(); @@ -154,9 +152,7 @@ public void popUntilViewport(IViewport viewport) { @Override public void translate(float x, float y) { - checkViewport(); - this.top.getMatrix().translate(vec(x, y, 0)); - this.top.markDirty(); + this.translate(x, y, 0.0f); } @Override @@ -169,7 +165,7 @@ public void translate(float x, float y, float z) { @Override public void rotate(float angle, float x, float y, float z) { checkViewport(); - this.top.getMatrix().rotate(angle, vec(x, y, z)); + this.top.getMatrix().rotate(angle, x, y, z); this.top.markDirty(); } @@ -181,7 +177,7 @@ public void rotateZ(float angle) { @Override public void scale(float x, float y) { checkViewport(); - this.top.getMatrix().scale(vec(x, y, 1f)); + this.top.getMatrix().scale(x, y, 1.0f); this.top.markDirty(); } @@ -264,9 +260,4 @@ public void applyTo(PoseStack poseStack) { public TransformationMatrix peek() { return this.top; } - - private static Vector3f vec(float x, float y, float z) { - sharedVec.set(x, y, z); - return sharedVec; - } } From 3bac0c6029cb9c7d6c9a3709ea85a5c8a65d1d97 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:26:14 +0300 Subject: [PATCH 40/84] fix interaction offset issues by requiring the parent menu's mouse positions to be passed into embed rendering --- .../emi/recipe/ModularUIEmiRecipe.java | 2 +- .../jei/recipe/ModularUIRecipeCategory.java | 2 +- .../rei/recipe/ModularUIREIDisplay.java | 3 +- .../brachy/modularui/screen/EmbedHandler.java | 32 +++++++++---------- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 28875f46..c042f1bd 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -174,7 +174,7 @@ public Bounds getBounds() { @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { ModularScreen screen = getModularScreen(this.recipe); - EmbedHandler.drawEmbed(screen, graphics, partialTick, r -> !(r instanceof SizedButtonWidget)); + EmbedHandler.drawEmbed(screen, graphics, mouseX, mouseY, partialTick, r -> !(r instanceof SizedButtonWidget)); } @Override diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 59a5b1b7..7bd0ca57 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -228,7 +228,7 @@ public void getTooltip(ITooltipBuilder tooltipBuilder, T recipe, IRecipeSlotsVie @Override public void draw(T recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) { ModularScreen screen = getModularScreen(recipe); - EmbedHandler.drawEmbed(screen, graphics, Minecraft.getInstance().getPartialTick()); + EmbedHandler.drawEmbed(screen, graphics, (int) mouseX, (int) mouseY, Minecraft.getInstance().getPartialTick()); } @Override diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index 506451c9..be2e877f 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -168,7 +168,8 @@ public UIWrapperWidget(ModularUIREIDisplay display, float offsetX, float offsetY @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { ModularScreen screen = getModularScreen(this.display); - EmbedHandler.drawEmbed(screen, graphics, partialTick, () -> { + // FIXME this thing breaks if you have the same recipe open in REI and pinned as a favorite. + EmbedHandler.drawEmbed(screen, graphics, mouseX, mouseY, partialTick, () -> { screen.getContext().pushMatrix(); screen.getContext().translate(this.offsetX, this.offsetY); }, () -> { diff --git a/src/main/java/brachy/modularui/screen/EmbedHandler.java b/src/main/java/brachy/modularui/screen/EmbedHandler.java index 55d1c4b7..5bab8d36 100644 --- a/src/main/java/brachy/modularui/screen/EmbedHandler.java +++ b/src/main/java/brachy/modularui/screen/EmbedHandler.java @@ -24,43 +24,43 @@ public static int getEmbedHeight(ModularScreen screen) { return screen.getMainPanel().getArea().height; } - public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float partialTicks) { - drawEmbed(screen, graphics, partialTicks, null, null); + public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { + drawEmbed(screen, graphics, mouseX, mouseY, partialTicks, null, null); } - public static void drawEmbedNoVanillaElements(ModularScreen screen, GuiGraphics graphics, float partialTicks) { - drawEmbedNoVanillaElements(screen, graphics, partialTicks, null, null); + public static void drawEmbedNoVanillaElements(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { + drawEmbedNoVanillaElements(screen, graphics, mouseX, mouseY, partialTicks, null, null); } - public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float partialTicks, + public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks, @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { - drawEmbed(screen, graphics, partialTicks, r -> true, beforeDraw, afterDraw); + drawEmbed(screen, graphics, mouseX, mouseY, partialTicks, r -> true, beforeDraw, afterDraw); } - public static void drawEmbedNoVanillaElements(ModularScreen screen, GuiGraphics graphics, float partialTicks, + public static void drawEmbedNoVanillaElements(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks, @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { - drawEmbed(screen, graphics, partialTicks, r -> false, beforeDraw, afterDraw); + drawEmbed(screen, graphics, mouseX, mouseY, partialTicks, r -> false, beforeDraw, afterDraw); } - public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float partialTicks, Predicate vanillaElementFilter) { - drawEmbed(screen, graphics, partialTicks, vanillaElementFilter, null, null); + public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks, + Predicate vanillaElementFilter) { + drawEmbed(screen, graphics, mouseX, mouseY, partialTicks, vanillaElementFilter, null, null); } - public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, float partialTicks, Predicate vanillaElementFilter, - @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { + public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks, + Predicate vanillaElementFilter, @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { screen.getContext().reset(); graphics.pose().pushPose(); if (beforeDraw != null) beforeDraw.run(); - var defContext = ClientScreenHandler.getDefaultContext(); - int mx = defContext.getAbsMouseX(); - int my = defContext.getAbsMouseY(); + int mx = screen.getContext().unTransformX(mouseX, mouseY); + int my = screen.getContext().unTransformY(mouseX, mouseY); screen.render(graphics, mx, my, partialTicks); if (vanillaElementFilter != null) { RenderSystem.disableDepthTest(); - ClientScreenHandler.drawVanillaElements(graphics, screen.getScreenWrapper().wrappedScreen(), mx, my, partialTicks, vanillaElementFilter); + ClientScreenHandler.drawVanillaElements(graphics, screen.getScreenWrapper().wrappedScreen(), mouseX, mouseY, partialTicks, vanillaElementFilter); RenderSystem.enableDepthTest(); } From d869ac2da9b3e671d52242ce8b8904e0ca789496 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:03:45 +0300 Subject: [PATCH 41/84] Fix Fucked Up and Evil render offset --- .../integration/emi/EmiRecipeViewerSlot.java | 3 --- .../integration/jei/JeiRecipeViewerSlot.java | 14 ++++++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index 91c10de0..12dbd705 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -51,10 +51,7 @@ protected void rebuildRealSlot() { @Override public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - context.graphicsPose().pushPose(); - context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); - context.graphicsPose().popPose(); } @Override diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 6c19a922..2ca44e42 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -28,6 +28,7 @@ import mezz.jei.library.gui.recipes.OutputSlotTooltipCallback; import mezz.jei.library.ingredients.DisplayIngredientAcceptor; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import java.util.ArrayList; @@ -39,7 +40,7 @@ @ApiStatus.Internal public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget> implements ISlottedRecipeWidget { - @Getter private @UnknownNullability IRecipeSlotDrawable slotWidget; + @Getter private @Nullable IRecipeSlotDrawable slotWidget; @Setter private IFocusGroup focuses; @Setter private ICycler cycler; @@ -79,7 +80,7 @@ protected void rebuildRealSlot() { } recipeSlot.modularui$setRole(mapToJeiRole(this.recipeSlotRole)); // mmm I love off-by-one errors - slotWidget.setPosition(this.getArea().x + 1, this.getArea().y + 1); + slotWidget.setPosition(1, 1); replaceSlotIngredients(recipeSlot); recipeSlot.modularui$setCycler(this.cycler); @@ -92,7 +93,8 @@ public void configureJeiSlotBuilder(IRecipeSlotBuilder builder) { .map(this.renderMappingFunction) .toList()); } - builder.setPosition(this.getArea().x + 1, this.getArea().y + 1); + // mmm I love off-by-one errors + builder.setPosition(1, 1); } // Mostly copied from RecipeSlotBuilder#build @@ -125,10 +127,7 @@ private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { if (this.slotWidget == null) return; - context.graphicsPose().pushPose(); - context.getGraphics().pose().translate(-this.getArea().x, -this.getArea().y, 0); this.slotWidget.draw(context.getGraphics()); - context.graphicsPose().popPose(); } /* @Override @@ -141,12 +140,11 @@ public Result onKeyPressed(int keyCode, int scanCode, int modifiers) { return this.slotWidget.keyPressed(keyCode, scanCode, modifiers) ? Result.SUCCESS : Result.ACCEPT; } */ - private static final ScreenPosition ZERO_POS = new ScreenPosition(0, 0); @Override public Optional getSlotUnderMouse(double mouseX, double mouseY) { if (isHovering() && slotWidget != null) { - return Optional.of(new RecipeSlotUnderMouse(slotWidget, ZERO_POS)); + return Optional.of(new RecipeSlotUnderMouse(slotWidget, getArea().x, getArea().y)); } return Optional.empty(); } From cfe6916617ecc3ed013ff72545cd2e0d7b4b8806 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:56:38 -0400 Subject: [PATCH 42/84] possible fixed issues with recipe widget caching by caching the builders for each recipe --- .../jei/recipe/ModularUIRecipeCategory.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 7bd0ca57..bdfc045a 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -14,6 +14,8 @@ import brachy.modularui.screen.ModularScreen; import brachy.modularui.screen.RichTooltip; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenRectangle; @@ -38,6 +40,7 @@ import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.function.Function; @ApiStatus.Experimental @@ -46,6 +49,7 @@ public abstract class ModularUIRecipeCategory implements IRecipeCategory { public static final String SCREEN_NAME_PREFIX = "jei_recipe_"; private final LoadingCache modularScreenCache; + private final Map recipeLayoutCache = new Object2ObjectOpenHashMap<>(); private final Function recipeUI; private final Function recipeIdGetter; @@ -58,7 +62,7 @@ protected ModularUIRecipeCategory(Function recipeUI, Function() { @@ -120,7 +124,12 @@ protected void calculateSize(T recipe) { private ModularScreen createScreen(T recipe) { ResourceLocation id = this.recipeIdGetter.apply(recipe); - return createScreen(this.recipeUI.apply(recipe), id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath()); + ModularScreen screen = createScreen(this.recipeUI.apply(recipe), id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath()); + if (recipeLayoutCache.containsKey(id)){ + IRecipeExtrasBuilder builder = recipeLayoutCache.get(id); + screen.getMainPanel().visitTransformAllChildren(widget -> transformWidget(builder, widget)); + } + return screen; } public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { @@ -186,6 +195,7 @@ public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget w @Override public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { + recipeLayoutCache.remove(recipeIdGetter.apply(recipe)); ModularScreen screen = getModularScreen(recipe); MutableInt index = new MutableInt(0); screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, index)); @@ -198,6 +208,7 @@ public void createRecipeExtras(IRecipeExtrasBuilder builder, T recipe, IFocusGro ModularScreen screen = getModularScreen(recipe); screen.getMainPanel().visitTransformAllChildren(widget -> transformWidget(builder, widget)); + recipeLayoutCache.put(recipeIdGetter.apply(recipe), builder); builder.addGuiEventListener(new ModularUIGuiEventListener(recipe)); } From 3f7c0523ec382a47479fa12a3cbd40875d6f0f93 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:07:33 +0300 Subject: [PATCH 43/84] Fix JEI screen cache issue --- .../jei/recipe/ModularUIRecipeCategory.java | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index bdfc045a..cdaacc69 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -14,8 +14,6 @@ import brachy.modularui.screen.ModularScreen; import brachy.modularui.screen.RichTooltip; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; - import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenRectangle; @@ -40,7 +38,6 @@ import java.time.Duration; import java.util.List; -import java.util.Map; import java.util.function.Function; @ApiStatus.Experimental @@ -49,7 +46,6 @@ public abstract class ModularUIRecipeCategory implements IRecipeCategory { public static final String SCREEN_NAME_PREFIX = "jei_recipe_"; private final LoadingCache modularScreenCache; - private final Map recipeLayoutCache = new Object2ObjectOpenHashMap<>(); private final Function recipeUI; private final Function recipeIdGetter; @@ -65,7 +61,6 @@ protected ModularUIRecipeCategory(Function recipeUI, Function() { - @Override public ModularScreen load(T recipe) { return ModularUIRecipeCategory.this.createScreen(recipe); @@ -124,12 +119,7 @@ protected void calculateSize(T recipe) { private ModularScreen createScreen(T recipe) { ResourceLocation id = this.recipeIdGetter.apply(recipe); - ModularScreen screen = createScreen(this.recipeUI.apply(recipe), id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath()); - if (recipeLayoutCache.containsKey(id)){ - IRecipeExtrasBuilder builder = recipeLayoutCache.get(id); - screen.getMainPanel().visitTransformAllChildren(widget -> transformWidget(builder, widget)); - } - return screen; + return createScreen(this.recipeUI.apply(recipe), id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath()); } public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { @@ -195,7 +185,6 @@ public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget w @Override public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { - recipeLayoutCache.remove(recipeIdGetter.apply(recipe)); ModularScreen screen = getModularScreen(recipe); MutableInt index = new MutableInt(0); screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, index)); @@ -208,8 +197,7 @@ public void createRecipeExtras(IRecipeExtrasBuilder builder, T recipe, IFocusGro ModularScreen screen = getModularScreen(recipe); screen.getMainPanel().visitTransformAllChildren(widget -> transformWidget(builder, widget)); - recipeLayoutCache.put(recipeIdGetter.apply(recipe), builder); - builder.addGuiEventListener(new ModularUIGuiEventListener(recipe)); + builder.addGuiEventListener(new ModularUIGuiEventListener(screen)); } @Override @@ -267,46 +255,46 @@ public int getWidth(T recipe) { return this.displayWidth; } - public class ModularUIGuiEventListener implements IJeiGuiEventListener { + public static class ModularUIGuiEventListener implements IJeiGuiEventListener { - private final T recipe; + private final ModularScreen screen; - public ModularUIGuiEventListener(T recipe) { - this.recipe = recipe; + public ModularUIGuiEventListener(ModularScreen screen) { + this.screen = screen; } public ScreenRectangle getArea() { - return getModularScreen(this.recipe).getMainRectangle(); + return this.screen.getMainRectangle(); } @Override public void mouseMoved(double mouseX, double mouseY) { - //getModularScreen(this.recipe).mouseMoved(mouseX, mouseY); + //this.screen.mouseMoved(mouseX, mouseY); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - return getModularScreen(this.recipe).mousePressed(button); + return this.screen.mousePressed(button); } @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { - return getModularScreen(this.recipe).mouseReleased(button); + return this.screen.mouseReleased(button); } @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { - return getModularScreen(this.recipe).mouseDragged(button, dragX, dragY); + return this.screen.mouseDragged(button, dragX, dragY); } @Override public boolean mouseScrolled(double mouseX, double mouseY, double scrollDelta) { - return getModularScreen(this.recipe).mouseScrolled(scrollDelta); + return this.screen.mouseScrolled(scrollDelta); } @Override public boolean keyPressed(double mouseX, double mouseY, int keyCode, int scanCode, int modifiers) { - return getModularScreen(this.recipe).keyPressed(keyCode, scanCode, modifiers); + return this.screen.keyPressed(keyCode, scanCode, modifiers); } } From d2ae6674cfbcc1e8ed9706e0d26766ff15bcd6d1 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:11:17 +0300 Subject: [PATCH 44/84] make the screen caches 1. have soft values 2. have a larger maximum size 3. expire after a longer time of not being used 4. have a larger initial capacity --- .../emi/recipe/ModularUIEmiRecipe.java | 8 +++++--- .../jei/recipe/ModularUIRecipeCategory.java | 8 +++++--- .../RecipeViewerCompatConstants.java | 19 +++++++++++++++++++ .../rei/recipe/ModularUIREIDisplay.java | 8 +++++--- 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index c042f1bd..89a508f4 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -5,6 +5,7 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.emi.EmiRecipeViewerSlot; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.RecipeViewerCompatConstants; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -29,7 +30,6 @@ import lombok.Getter; import org.jetbrains.annotations.ApiStatus; -import java.time.Duration; import java.util.Iterator; import java.util.List; import java.util.function.Supplier; @@ -39,8 +39,10 @@ public abstract class ModularUIEmiRecipe implements EmiRecipe { private static final String SCREEN_NAME_PREFIX = "emi_recipe_"; private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(Duration.ofSeconds(1)) - .maximumSize(20) + .expireAfterAccess(RecipeViewerCompatConstants.CACHE_EXPIRY_TIME) + .initialCapacity(RecipeViewerCompatConstants.GOOD_CACHE_INITIAL_SIZE) + .maximumSize(RecipeViewerCompatConstants.EXPECTED_GOOD_CACHE_SIZE) + .softValues() .build(new CacheLoader<>() { @Override public ModularScreen load(ModularUIEmiRecipe key) { diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index cdaacc69..0e11648a 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -8,6 +8,7 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; +import brachy.modularui.integration.recipeviewer.RecipeViewerCompatConstants; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -36,7 +37,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import java.time.Duration; import java.util.List; import java.util.function.Function; @@ -58,8 +58,10 @@ protected ModularUIRecipeCategory(Function recipeUI, Function() { @Override public ModularScreen load(T recipe) { diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java new file mode 100644 index 00000000..00212b7b --- /dev/null +++ b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java @@ -0,0 +1,19 @@ +package brachy.modularui.integration.recipeviewer; + +import org.jetbrains.annotations.ApiStatus; + +import java.time.Duration; + +/** + * Constants for the recipe viewer compats' caches + */ +@ApiStatus.NonExtendable +public interface RecipeViewerCompatConstants { + + int EXPECTED_MAX_CATEGORY_RECIPES = 128; + int EXPECTED_CATEGORIES_COUNT = 32; + + int EXPECTED_GOOD_CACHE_SIZE = EXPECTED_MAX_CATEGORY_RECIPES * EXPECTED_CATEGORIES_COUNT; + int GOOD_CACHE_INITIAL_SIZE = EXPECTED_MAX_CATEGORY_RECIPES / 6; + Duration CACHE_EXPIRY_TIME = Duration.ofSeconds(60); +} diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index be2e877f..0df2b5e8 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -5,6 +5,7 @@ import brachy.modularui.api.widget.IWidget; import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.RecipeViewerCompatConstants; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.screen.EmbedHandler; @@ -31,7 +32,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -44,8 +44,10 @@ public abstract class ModularUIREIDisplay implements Display { private static final String SCREEN_NAME_PREFIX = "rei_display_"; private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(Duration.ofSeconds(1)) - .maximumSize(20) + .expireAfterAccess(RecipeViewerCompatConstants.CACHE_EXPIRY_TIME) + .initialCapacity(RecipeViewerCompatConstants.GOOD_CACHE_INITIAL_SIZE) + .maximumSize(RecipeViewerCompatConstants.EXPECTED_GOOD_CACHE_SIZE) + .softValues() .build(new CacheLoader<>() { @Override public ModularScreen load(ModularUIREIDisplay key) { From a2309679bcba78b6f716a47aa2872121cc4f30a7 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:15:17 +0300 Subject: [PATCH 45/84] Make JEI compat users have to add their inputs and outputs themselves like other compats have --- .../jei/recipe/ModularUIRecipeCategory.java | 27 +++++++++++-------- .../modularui/test/TestRecipeViewerGuis.java | 6 +++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 0e11648a..cad10576 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -113,11 +113,13 @@ protected void calculateSize(T recipe) { public abstract int getMaxHeight(); /** - * Customize your recipe displays here. + * Add your recipes' inputs and outputs here. This is used by JEI for lookups to figure out what ingredients are inputs and + * outputs for a recipe. *

- * This is used by JEI for lookups to figure out what ingredients are inputs and outputs for a recipe. + * Note that you can only add inputs and outputs for JEI's recipe lookup/search here, as the layout builder that's + * passed into this method only handles those and not the displayed recipe previews. */ - public abstract void createRecipeDisplay(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses); + public abstract void setupRecipeIngredients(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses); private ModularScreen createScreen(T recipe) { ResourceLocation id = this.recipeIdGetter.apply(recipe); @@ -163,11 +165,9 @@ public IWidget transformWidget(IRecipeExtrasBuilder builder, IWidget widget) { @SuppressWarnings({ "rawtypes", "unchecked" }) @ApiStatus.OverrideOnly public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget widget, T recipe, IFocusGroup focuses, MutableInt index) { - // guard against JEMI issues - if (!(builder instanceof RecipeLayoutBuilderAccessor recipeLayoutBuilder)) return widget; if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; - recipeViewerSlot.setIngredientManager(recipeLayoutBuilder.modularui$getIngredientManager()); + recipeViewerSlot.setIngredientManager(((RecipeLayoutBuilderAccessor) builder).modularui$getIngredientManager()); recipeViewerSlot.setRecipeCategory(this); recipeViewerSlot.setRecipe(recipe); recipeViewerSlot.setFocuses(focuses); @@ -187,11 +187,16 @@ public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget w @Override public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { - ModularScreen screen = getModularScreen(recipe); - MutableInt index = new MutableInt(0); - screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, index)); - - this.createRecipeDisplay(builder, recipe, focuses); + // guard against JEMI issues by explicitly checking for JEI's implementation + // this is also done to skip having to create the whole widget tree when JEI is only looking up the recipe's ingredients + if (builder instanceof RecipeLayoutBuilderAccessor) { + ModularScreen screen = getModularScreen(recipe); + MutableInt index = new MutableInt(0); + screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, index)); + } else { + // don't bother with creating the full widget tree if setRecipe was called to get the recipe's ingredients + this.setupRecipeIngredients(builder, recipe, focuses); + } } @Override diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index 286af093..7eda907d 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -35,6 +35,7 @@ import me.shedaniel.rei.api.common.util.EntryStacks; import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.recipe.RecipeType; import mezz.jei.api.registration.IRecipeCatalystRegistration; import mezz.jei.api.registration.IRecipeCategoryRegistration; @@ -173,8 +174,9 @@ public int getMaxHeight() { } @Override - public void createRecipeDisplay(IRecipeLayoutBuilder builder, TestMachine.Recipe recipe, IFocusGroup focuses) { - calculateSize(recipe); + public void setupRecipeIngredients(IRecipeLayoutBuilder builder, TestMachine.Recipe recipe, IFocusGroup focuses) { + recipe.in.forEach(item -> builder.addSlot(RecipeIngredientRole.INPUT).addItemStack(item)); + recipe.out.forEach(item -> builder.addSlot(RecipeIngredientRole.OUTPUT).addItemStack(item)); } @Override From f0304255733e502b6544908632332fc963bcc7b1 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:28:11 +0300 Subject: [PATCH 46/84] Fix JEI category size caching --- .../core/mixins/jei/RecipeGuiLogicMixin.java | 25 +++++++++ .../jei/recipe/ModularUIRecipeCategory.java | 56 +++++++++++++------ .../modularui/test/TestRecipeViewerGuis.java | 1 + src/main/resources/modularui.mixins.json | 1 + 4 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 src/main/java/brachy/modularui/core/mixins/jei/RecipeGuiLogicMixin.java diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeGuiLogicMixin.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeGuiLogicMixin.java new file mode 100644 index 00000000..fc8e0015 --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeGuiLogicMixin.java @@ -0,0 +1,25 @@ +package brachy.modularui.core.mixins.jei; + +import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; + +import com.llamalad7.mixinextras.sugar.Local; +import mezz.jei.api.recipe.category.IRecipeCategory; +import mezz.jei.gui.recipes.RecipeGuiLogic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(value = RecipeGuiLogic.class, remap = false) +public class RecipeGuiLogicMixin { + + @ModifyVariable(method = "getVisibleRecipeLayoutsWithButtons", at = @At(value = "STORE"), name = "recipeHeight") + private int modularui$useCategoryMaxHeightForLayout(int recipeHeight, + @Local(name = "recipeCategory") IRecipeCategory recipeCategory) { + // if this is a MUI category, force the gui to use the category's max height as the recipe height + // (instead of the height of the first recipe it finds, which may be shorter than others) + if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + return muiCategory.getMaxHeight(); + } + return recipeHeight; + } +} diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index cad10576..4af0e8b5 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -35,6 +35,7 @@ import mezz.jei.api.recipe.category.IRecipeCategory; import org.apache.commons.lang3.mutable.MutableInt; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.MustBeInvokedByOverriders; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -46,13 +47,11 @@ public abstract class ModularUIRecipeCategory implements IRecipeCategory { public static final String SCREEN_NAME_PREFIX = "jei_recipe_"; private final LoadingCache modularScreenCache; + private final LoadingCache displaySizeCache; private final Function recipeUI; private final Function recipeIdGetter; - private boolean sizeCalculated = false; - private int displayWidth, displayHeight; - protected ModularUIRecipeCategory(Function recipeUI, Function recipeIdGetter) { this.recipeUI = recipeUI; this.recipeIdGetter = recipeIdGetter; @@ -65,7 +64,20 @@ protected ModularUIRecipeCategory(Function recipeUI, Function() { @Override public ModularScreen load(T recipe) { - return ModularUIRecipeCategory.this.createScreen(recipe); + try { + return ModularUIRecipeCategory.this.createScreen(recipe); + } finally { + ModularUIRecipeCategory.this.displaySizeCache.refresh(recipe); + } + } + }); + this.displaySizeCache = CacheBuilder.newBuilder() + .initialCapacity(RecipeViewerCompatConstants.GOOD_CACHE_INITIAL_SIZE) + .maximumSize(RecipeViewerCompatConstants.EXPECTED_MAX_CATEGORY_RECIPES) + .build(new CacheLoader<>() { + @Override + public Dimensions load(T recipe) { + return ModularUIRecipeCategory.this.calculateSize(recipe); } }); } @@ -74,14 +86,11 @@ private ModularScreen getModularScreen(T recipe) { return this.modularScreenCache.getUnchecked(recipe); } + @ApiStatus.OverrideOnly /** - * Calculates the size of the recipe if not already done.
- * This should be called in subclasses' {@link #createRecipeDisplay(IRecipeLayoutBuilder, Object, IFocusGroup) createRecipeDisplay} methods. - * Otherwise, the size of ALL the recipes in the same category are calculated at once, which can make the game lag for a few seconds. + * Calculates the size of the recipe. */ - protected void calculateSize(T recipe) { - if (this.sizeCalculated) return; - this.sizeCalculated = true; + protected Dimensions calculateSize(T recipe) { IWidget ui = this.recipeUI.apply(recipe); ResourceLocation id = this.recipeIdGetter.apply(recipe); int w = ui.resizer().getFixedPixelWidth(), h = ui.resizer().getFixedPixelHeight(); @@ -90,8 +99,16 @@ protected void calculateSize(T recipe) { w = EmbedHandler.getEmbedWidth(screen); h = EmbedHandler.getEmbedHeight(screen); } - this.displayWidth = w; - this.displayHeight = h; + return new Dimensions(w, h); + } + + /** + * Calculates and caches the size of the recipe if not already done.
+ * This should be called in subclasses' {@link #setupRecipeIngredients(IRecipeLayoutBuilder, Object, IFocusGroup) setupRecipeIngredients} methods. + * Otherwise, the size of ALL the recipes in the same category are calculated at once, which can make the game lag for a few seconds. + */ + protected final void calculateAndCacheSize(T recipe) { + this.displaySizeCache.getUnchecked(recipe); } /** @@ -119,7 +136,10 @@ protected void calculateSize(T recipe) { * Note that you can only add inputs and outputs for JEI's recipe lookup/search here, as the layout builder that's * passed into this method only handles those and not the displayed recipe previews. */ - public abstract void setupRecipeIngredients(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses); + @MustBeInvokedByOverriders + public void setupRecipeIngredients(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { + calculateAndCacheSize(recipe); + } private ModularScreen createScreen(T recipe) { ResourceLocation id = this.recipeIdGetter.apply(recipe); @@ -248,8 +268,8 @@ public final int getHeight() { } public int getHeight(T recipe) { - calculateSize(recipe); - return this.displayHeight; + Dimensions size = this.displaySizeCache.getUnchecked(recipe); + return size.height; } @Override @@ -258,10 +278,12 @@ public final int getWidth() { } public int getWidth(T recipe) { - calculateSize(recipe); - return this.displayWidth; + Dimensions display = this.displaySizeCache.getUnchecked(recipe); + return display.width; } + protected record Dimensions(int width, int height) { } + public static class ModularUIGuiEventListener implements IJeiGuiEventListener { private final ModularScreen screen; diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index 7eda907d..943d1f9e 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -175,6 +175,7 @@ public int getMaxHeight() { @Override public void setupRecipeIngredients(IRecipeLayoutBuilder builder, TestMachine.Recipe recipe, IFocusGroup focuses) { + super.setupRecipeIngredients(builder, recipe, focuses); recipe.in.forEach(item -> builder.addSlot(RecipeIngredientRole.INPUT).addItemStack(item)); recipe.out.forEach(item -> builder.addSlot(RecipeIngredientRole.OUTPUT).addItemStack(item)); } diff --git a/src/main/resources/modularui.mixins.json b/src/main/resources/modularui.mixins.json index f9692041..1e805415 100755 --- a/src/main/resources/modularui.mixins.json +++ b/src/main/resources/modularui.mixins.json @@ -22,6 +22,7 @@ "common.ComponentSerializerMixin", "common.SlotAccessor", "common.TextColorMixin", + "jei.RecipeGuiLogicMixin", "jei.RecipeLayoutAccessor", "jei.RecipeLayoutBuilderAccessor", "jei.RecipeLayoutBuilderMixin", From 16a0395542b79eb3ed13b887a92c72ebbc2438e0 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:29:29 +0300 Subject: [PATCH 47/84] obtain an ingredient manager if we don't have one and it's possible --- .../modularui/integration/jei/JeiRecipeViewerSlot.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 2ca44e42..1a03a49a 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -99,6 +99,13 @@ public void configureJeiSlotBuilder(IRecipeSlotBuilder builder) { // Mostly copied from RecipeSlotBuilder#build private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { + if (this.ingredientManager == null) { + if (ModularUIJeiPlugin.jeiHelpers == null) { + return; + } + this.ingredientManager = ModularUIJeiPlugin.jeiHelpers.getIngredientManager(); + } + final DisplayIngredientAcceptor ingredients = new DisplayIngredientAcceptor(this.ingredientManager); ingredients.addIngredientsUnsafe(this.entries.getStacks().stream() .map(this.renderMappingFunction) From 421556d581edc90bf8d2a35e42611de8f7f88ec3 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:43:54 +0300 Subject: [PATCH 48/84] Make the caches a LOT smaller, they have no reason to be **that** big --- .../integration/jei/recipe/ModularUIRecipeCategory.java | 4 ++-- .../recipeviewer/RecipeViewerCompatConstants.java | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 4af0e8b5..eceb6c0e 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -59,7 +59,7 @@ protected ModularUIRecipeCategory(Function recipeUI, Function() { @Override @@ -73,7 +73,7 @@ public ModularScreen load(T recipe) { }); this.displaySizeCache = CacheBuilder.newBuilder() .initialCapacity(RecipeViewerCompatConstants.GOOD_CACHE_INITIAL_SIZE) - .maximumSize(RecipeViewerCompatConstants.EXPECTED_MAX_CATEGORY_RECIPES) + .maximumSize(RecipeViewerCompatConstants.EXPECTED_GOOD_CACHE_SIZE) .build(new CacheLoader<>() { @Override public Dimensions load(T recipe) { diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java index 00212b7b..8f5cde20 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java @@ -10,10 +10,7 @@ @ApiStatus.NonExtendable public interface RecipeViewerCompatConstants { - int EXPECTED_MAX_CATEGORY_RECIPES = 128; - int EXPECTED_CATEGORIES_COUNT = 32; - - int EXPECTED_GOOD_CACHE_SIZE = EXPECTED_MAX_CATEGORY_RECIPES * EXPECTED_CATEGORIES_COUNT; - int GOOD_CACHE_INITIAL_SIZE = EXPECTED_MAX_CATEGORY_RECIPES / 6; + int EXPECTED_GOOD_CACHE_SIZE = 64; + int GOOD_CACHE_INITIAL_SIZE = EXPECTED_GOOD_CACHE_SIZE / 8; Duration CACHE_EXPIRY_TIME = Duration.ofSeconds(60); } From 8079fc11356165fe92ac89188a7cfede2b4f1acf Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:38:03 +0300 Subject: [PATCH 49/84] don't add the "open recipe viewer" click area in the recipe viewer --- .../java/brachy/modularui/test/TestMachine.java | 17 ++++++++++------- .../modularui/test/TestRecipeViewerGuis.java | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index c86e2b6e..8964e885 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -98,7 +98,7 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UI .coverChildren(176, 30) .padding(7) .widgetTheme(IThemeApi.PANEL) - .child(Recipes.buildMachineUI(panel, this.input, this.output, new DoubleSyncValue(this::getProgress).allowC2S())) + .child(Recipes.buildMachineUI(panel, this.input, this.output, new DoubleSyncValue(this::getProgress), true)) .child(new ParentWidget<>() .coverChildren() .decoration() @@ -258,7 +258,8 @@ private static boolean extract(ItemStack stack, IItemHandler handler, boolean si return false; } - public static IWidget buildMachineUI(ModularPanel panel, IItemHandler in, IItemHandler out, IDoubleValue progress) { + public static IWidget buildMachineUI(ModularPanel panel, IItemHandler in, IItemHandler out, IDoubleValue progress, + final boolean addRecipeViewerClickArea) { var val = new StringValue("Option 1"); IPanelHandler panelHandler = IPanelHandler.simple(panel, (parent, player) -> { return new ModularPanel<>("test_sub_panel").size(50).overlay(Text.str("Test")); @@ -292,11 +293,13 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler in, IIt .size(20) .texture(GuiTextures.PROGRESS_ARROW, ProgressDrawable.Direction.RIGHT) .configure(w -> { - w.listenGuiAction((IGuiAction.MouseReleased) (ctx, button) -> { - if (!ctx.isMouseAbove(w)) return false; - TestRecipeViewerGuis.openTestRecipeViewerCategory(); - return true; - }); + if (addRecipeViewerClickArea) { + w.listenGuiAction((IGuiAction.MouseReleased) (ctx, button) -> { + if (!ctx.isMouseAbove(w)) return false; + TestRecipeViewerGuis.openTestRecipeViewerCategory(); + return true; + }); + } })) .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() .slot(new ModularSlot(out, i).canPut(false)) diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index 943d1f9e..c89169f0 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -62,7 +62,7 @@ public static IWidget buildViewerUI(TestMachine.Recipe recipe) { var panel = new ModularPanel<>("recipe_viewer_recipe") .coverChildren(60, 40) .invisible(); - IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_INFINITE_ITEM_HANDLER, DoubleValue.simulateProgress(5000)); + IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_INFINITE_ITEM_HANDLER, DoubleValue.simulateProgress(5000), false); recipeUI.visitTransformAllChildren(w -> { if (w instanceof ItemSlot slot) { List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.in : recipe.out; From 65b8d57dd136783ef66987fd76e0043e1d242d41 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:38:36 +0300 Subject: [PATCH 50/84] Add 95 more testing recipes at random to test the preview cache --- .../brachy/modularui/test/TestMachine.java | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index 8964e885..0385a646 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -12,6 +12,7 @@ import brachy.modularui.drawable.progress.ProgressDrawable; import brachy.modularui.factory.PosGuiData; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; +import brachy.modularui.integration.recipeviewer.RecipeViewerCompatConstants; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; import brachy.modularui.screen.UISettings; @@ -31,9 +32,13 @@ import brachy.modularui.widgets.slot.ItemSlot; import brachy.modularui.widgets.slot.ModularSlot; +import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; @@ -172,7 +177,7 @@ public Recipe(String name) { } public Recipe in(ItemStack stack) { - this.in.add(stack); + if (!stack.isEmpty()) this.in.add(stack); return this; } @@ -185,7 +190,7 @@ public Recipe in(Item item) { } public Recipe out(ItemStack stack) { - this.out.add(stack); + if (!stack.isEmpty()) this.out.add(stack); return this; } @@ -220,15 +225,40 @@ public void finishRecipe(IItemHandler handler) { public static class Recipes { - public static final List list = new ArrayList<>(); - - static { + public static final List list = Util.make(new ArrayList<>(), list -> { list.add(new Recipe("/stuff_to_nether_star") .in(Items.DIAMOND) .in(Items.EMERALD) .in(Items.GOLD_INGOT, 4) .out(Items.NETHER_STAR)); - } + + // add more recipes with (consistent) random inputs and outputs to try to overflow the cache for testing + final int TOO_MANY_RECIPES_FOR_CACHE = RecipeViewerCompatConstants.EXPECTED_GOOD_CACHE_SIZE * 3 / 2 - 1; + RandomSource rng = RandomSource.create("this is a randomly picked seed.".hashCode()); + + for (int i = 0; i < TOO_MANY_RECIPES_FOR_CACHE; i++) { + int inAmount = Math.min(i % 4 + 1, 4); + int outAmount = Math.min((i + 3) % 4 + 1, 4); + Recipe recipe = new Recipe("/random_" + i + "_in-" + inAmount + "_out-" + outAmount); + + while (recipe.in.size() < inAmount) { + BuiltInRegistries.ITEM.getRandom(rng).ifPresent(item -> { + ItemStack stack = new ItemStack(item); + stack.setCount(Mth.clamp((int) (rng.nextGaussian() * stack.getMaxStackSize()), 0, stack.getMaxStackSize())); + recipe.in(stack); + }); + } + while (recipe.out.size() < outAmount) { + BuiltInRegistries.ITEM.getRandom(rng).ifPresent(item -> { + ItemStack stack = new ItemStack(item); + stack.setCount(Mth.clamp((int) (rng.nextGaussian() * stack.getMaxStackSize()), 0, stack.getMaxStackSize())); + recipe.out(stack); + }); + } + + list.add(recipe); + } + }); public static Recipe findRecipe(IItemHandler input, @Nullable Recipe lastRecipe) { if (lastRecipe != null && lastRecipe.startRecipe(input, true)) return lastRecipe; From b4231edefd27a8e5807bf8e90bc77122fb54ba70 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:09:37 +0300 Subject: [PATCH 51/84] ah, screw it. just use soft references for the values. it works fine. --- .../emi/recipe/ModularUIEmiRecipe.java | 13 +++++++++---- .../jei/recipe/ModularUIRecipeCategory.java | 18 ++++++++++++------ .../RecipeViewerCompatConstants.java | 16 ---------------- .../rei/recipe/ModularUIREIDisplay.java | 13 +++++++++---- .../brachy/modularui/test/TestMachine.java | 13 ++++++++----- 5 files changed, 38 insertions(+), 35 deletions(-) delete mode 100644 src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 89a508f4..e10ea9be 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -5,7 +5,6 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.emi.EmiRecipeViewerSlot; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; -import brachy.modularui.integration.recipeviewer.RecipeViewerCompatConstants; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -19,6 +18,8 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import dev.emi.emi.api.recipe.EmiRecipe; import dev.emi.emi.api.stack.EmiIngredient; import dev.emi.emi.api.stack.EmiStack; @@ -39,15 +40,19 @@ public abstract class ModularUIEmiRecipe implements EmiRecipe { private static final String SCREEN_NAME_PREFIX = "emi_recipe_"; private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(RecipeViewerCompatConstants.CACHE_EXPIRY_TIME) - .initialCapacity(RecipeViewerCompatConstants.GOOD_CACHE_INITIAL_SIZE) - .maximumSize(RecipeViewerCompatConstants.EXPECTED_GOOD_CACHE_SIZE) + .initialCapacity(64) .softValues() .build(new CacheLoader<>() { @Override public ModularScreen load(ModularUIEmiRecipe key) { return key.createScreen(); } + + @Override + public ListenableFuture reload(ModularUIEmiRecipe key, ModularScreen oldValue) { + // if an old value is (somehow) available, reuse it + return Futures.immediateFuture(oldValue); + } }); private static ModularScreen getModularScreen(ModularUIEmiRecipe recipe) { diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index eceb6c0e..6a902b49 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -8,7 +8,6 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; -import brachy.modularui.integration.recipeviewer.RecipeViewerCompatConstants; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -23,6 +22,8 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; import mezz.jei.api.gui.builder.IRecipeSlotBuilder; import mezz.jei.api.gui.builder.ITooltipBuilder; @@ -46,6 +47,8 @@ public abstract class ModularUIRecipeCategory implements IRecipeCategory { public static final String SCREEN_NAME_PREFIX = "jei_recipe_"; + // These don't need special clearing because they're instance fields so the category instance being recreated by JEI is enough to + // free the class instance for GC (and thus free & clear these) private final LoadingCache modularScreenCache; private final LoadingCache displaySizeCache; @@ -57,9 +60,7 @@ protected ModularUIRecipeCategory(Function recipeUI, Function() { @Override @@ -70,10 +71,15 @@ public ModularScreen load(T recipe) { ModularUIRecipeCategory.this.displaySizeCache.refresh(recipe); } } + + @Override + public ListenableFuture reload(T key, ModularScreen oldValue) { + // if an old value is (somehow) available, reuse it + return Futures.immediateFuture(oldValue); + } }); this.displaySizeCache = CacheBuilder.newBuilder() - .initialCapacity(RecipeViewerCompatConstants.GOOD_CACHE_INITIAL_SIZE) - .maximumSize(RecipeViewerCompatConstants.EXPECTED_GOOD_CACHE_SIZE) + .initialCapacity(64) .build(new CacheLoader<>() { @Override public Dimensions load(T recipe) { diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java deleted file mode 100644 index 8f5cde20..00000000 --- a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerCompatConstants.java +++ /dev/null @@ -1,16 +0,0 @@ -package brachy.modularui.integration.recipeviewer; - -import org.jetbrains.annotations.ApiStatus; - -import java.time.Duration; - -/** - * Constants for the recipe viewer compats' caches - */ -@ApiStatus.NonExtendable -public interface RecipeViewerCompatConstants { - - int EXPECTED_GOOD_CACHE_SIZE = 64; - int GOOD_CACHE_INITIAL_SIZE = EXPECTED_GOOD_CACHE_SIZE / 8; - Duration CACHE_EXPIRY_TIME = Duration.ofSeconds(60); -} diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index 0df2b5e8..39b133c5 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -5,7 +5,6 @@ import brachy.modularui.api.widget.IWidget; import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; -import brachy.modularui.integration.recipeviewer.RecipeViewerCompatConstants; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.screen.EmbedHandler; @@ -21,6 +20,8 @@ import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.resources.ResourceLocation; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.Getter; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.gui.widgets.Tooltip; @@ -44,15 +45,19 @@ public abstract class ModularUIREIDisplay implements Display { private static final String SCREEN_NAME_PREFIX = "rei_display_"; private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(RecipeViewerCompatConstants.CACHE_EXPIRY_TIME) - .initialCapacity(RecipeViewerCompatConstants.GOOD_CACHE_INITIAL_SIZE) - .maximumSize(RecipeViewerCompatConstants.EXPECTED_GOOD_CACHE_SIZE) + .initialCapacity(64) .softValues() .build(new CacheLoader<>() { @Override public ModularScreen load(ModularUIREIDisplay key) { return key.createScreen(); } + + @Override + public ListenableFuture reload(ModularUIREIDisplay key, ModularScreen oldValue) { + // if an old value is (somehow) available, reuse it + return Futures.immediateFuture(oldValue); + } }); private static ModularScreen getModularScreen(ModularUIREIDisplay display) { diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index 0385a646..a77e34fe 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -12,7 +12,6 @@ import brachy.modularui.drawable.progress.ProgressDrawable; import brachy.modularui.factory.PosGuiData; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; -import brachy.modularui.integration.recipeviewer.RecipeViewerCompatConstants; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.ModularScreen; import brachy.modularui.screen.UISettings; @@ -232,11 +231,15 @@ public static class Recipes { .in(Items.GOLD_INGOT, 4) .out(Items.NETHER_STAR)); - // add more recipes with (consistent) random inputs and outputs to try to overflow the cache for testing - final int TOO_MANY_RECIPES_FOR_CACHE = RecipeViewerCompatConstants.EXPECTED_GOOD_CACHE_SIZE * 3 / 2 - 1; - RandomSource rng = RandomSource.create("this is a randomly picked seed.".hashCode()); + // add (many) more recipes with (consistent) random inputs and outputs to try to overflow the cache for testing + final int VERY_MANY_RECIPES = 256; + RandomSource rng = RandomSource.create(""" + // chosen by fair dice roll. + // guaranteed to be random. + """.hashCode()); - for (int i = 0; i < TOO_MANY_RECIPES_FOR_CACHE; i++) { + // start counting from 1 so we get exactly 256 recipes + for (int i = 1; i < VERY_MANY_RECIPES; i++) { int inAmount = Math.min(i % 4 + 1, 4); int outAmount = Math.min((i + 3) % 4 + 1, 4); Recipe recipe = new Recipe("/random_" + i + "_in-" + inAmount + "_out-" + outAmount); From 532e7bad835e3236bb4c899a12009fd30165b844 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:11:55 +0300 Subject: [PATCH 52/84] clear the screen caches on reload BUT block anyone else from clearing them --- .../modularui/integration/emi/ModularUIEmiPlugin.java | 8 ++++++++ .../integration/emi/recipe/ModularUIEmiRecipe.java | 10 ++++++++++ .../jei/recipe/ModularUIRecipeCategory.java | 2 +- .../modularui/integration/rei/ModularUIREIPlugin.java | 9 +++++++++ .../integration/rei/recipe/ModularUIREIDisplay.java | 11 +++++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java b/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java index 5df34a5c..c51c7a7d 100644 --- a/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java +++ b/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java @@ -2,18 +2,26 @@ import brachy.modularui.ModularUI; import brachy.modularui.integration.emi.handler.EmiScreenHandler; +import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; import brachy.modularui.screen.ContainerScreenWrapper; import brachy.modularui.screen.ScreenWrapper; import brachy.modularui.test.TestRecipeViewerGuis; import dev.emi.emi.api.EmiEntrypoint; +import dev.emi.emi.api.EmiInitRegistry; import dev.emi.emi.api.EmiPlugin; import dev.emi.emi.api.EmiRegistry; @EmiEntrypoint public class ModularUIEmiPlugin implements EmiPlugin { + /// This is only used to clear the stale recipe display "screen" cache. + @Override + public void initialize(EmiInitRegistry registry) { + ModularUIEmiRecipe.clearScreenCache(); + } + @Override public void register(EmiRegistry registry) { EmiScreenHandler.register(ScreenWrapper.class, registry); diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index e10ea9be..0d18b593 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -227,4 +227,14 @@ public boolean mouseReleased(int button) { return getModularScreen(this.recipe).mouseReleased(button); } } + + private static final StackWalker STAR_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + + @ApiStatus.Internal + public static void clearScreenCache() { + if (!STAR_WALKER.getCallerClass().equals(ModularUIEmiRecipe.class)) { + throw new IllegalCallerException("Attempted to call ModularUIEmiRecipe#clearScreenCache!"); + } + SCREEN_CACHE.invalidateAll(); + } } diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java index 6a902b49..e2a6f34d 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java @@ -92,10 +92,10 @@ private ModularScreen getModularScreen(T recipe) { return this.modularScreenCache.getUnchecked(recipe); } - @ApiStatus.OverrideOnly /** * Calculates the size of the recipe. */ + @ApiStatus.OverrideOnly protected Dimensions calculateSize(T recipe) { IWidget ui = this.recipeUI.apply(recipe); ResourceLocation id = this.recipeIdGetter.apply(recipe); diff --git a/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java b/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java index 84569874..42ef2726 100644 --- a/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java +++ b/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java @@ -2,11 +2,13 @@ import brachy.modularui.ModularUI; import brachy.modularui.integration.rei.handler.REIScreenHandler; +import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; import brachy.modularui.screen.ContainerScreenWrapper; import brachy.modularui.screen.ScreenWrapper; import brachy.modularui.test.TestRecipeViewerGuis; +import me.shedaniel.rei.api.client.entry.renderer.EntryRendererRegistry; import me.shedaniel.rei.api.client.plugins.REIClientPlugin; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; @@ -17,6 +19,13 @@ @REIPluginClient public class ModularUIREIPlugin implements REIClientPlugin { + /// This is only used to clear the stale recipe display "screen" cache. + @SuppressWarnings("UnstableApiUsage") + @Override + public void registerEntryRenderers(EntryRendererRegistry registry) { + ModularUIREIDisplay.clearScreenCache(); + } + @Override public void registerExclusionZones(ExclusionZones zones) { zones.register(ScreenWrapper.class, REIScreenHandler.of(ScreenWrapper.class)); diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java index 39b133c5..9d9c5396 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java @@ -6,6 +6,7 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; +import brachy.modularui.integration.rei.ModularUIREIPlugin; import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -255,4 +256,14 @@ public boolean charTyped(char codePoint, int modifiers) { return getModularScreen(this.display).charTyped(codePoint, modifiers); } } + + private static final StackWalker BETTER_CALL_SAUL = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + + @ApiStatus.Internal + public static void clearScreenCache() { + if (!BETTER_CALL_SAUL.getCallerClass().equals(ModularUIREIPlugin.class)) { + throw new IllegalCallerException("Attempted to call ModularUIREIDisplay#clearScreenCache!"); + } + SCREEN_CACHE.invalidateAll(); + } } From 43510c36bd330dd06b6017cde0de66de04172675 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:20:16 +0300 Subject: [PATCH 53/84] Move handler utils to brachy.modularui.utils.handlers --- src/main/java/brachy/modularui/test/TestBlockEntity.java | 2 +- .../handlers/fluid/CycleFluidEntryHandler.java | 2 +- .../recipeviewer => utils}/handlers/fluid/EmptyFluidTank.java | 2 +- .../utils/{ => handlers/fluid}/FluidTankHandler.java | 2 +- .../utils/{ => handlers/fluid}/IMultiFluidTankHandler.java | 2 +- .../utils/{ => handlers/fluid}/MultiFluidTankHandler.java | 2 +- .../handlers/item/CycleItemEntryHandler.java | 2 +- .../brachy/modularui/value/sync/FluidSlotSyncHandler.java | 4 ++-- src/main/java/brachy/modularui/widgets/slot/FluidSlot.java | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) rename src/main/java/brachy/modularui/{integration/recipeviewer => utils}/handlers/fluid/CycleFluidEntryHandler.java (97%) rename src/main/java/brachy/modularui/{integration/recipeviewer => utils}/handlers/fluid/EmptyFluidTank.java (94%) rename src/main/java/brachy/modularui/utils/{ => handlers/fluid}/FluidTankHandler.java (97%) mode change 100755 => 100644 rename src/main/java/brachy/modularui/utils/{ => handlers/fluid}/IMultiFluidTankHandler.java (81%) rename src/main/java/brachy/modularui/utils/{ => handlers/fluid}/MultiFluidTankHandler.java (98%) rename src/main/java/brachy/modularui/{integration/recipeviewer => utils}/handlers/item/CycleItemEntryHandler.java (97%) diff --git a/src/main/java/brachy/modularui/test/TestBlockEntity.java b/src/main/java/brachy/modularui/test/TestBlockEntity.java index c0125732..716e6d26 100644 --- a/src/main/java/brachy/modularui/test/TestBlockEntity.java +++ b/src/main/java/brachy/modularui/test/TestBlockEntity.java @@ -18,7 +18,7 @@ import brachy.modularui.utils.Alignment; import brachy.modularui.utils.Color; import brachy.modularui.utils.Interpolation; -import brachy.modularui.utils.MultiFluidTankHandler; +import brachy.modularui.utils.handlers.fluid.MultiFluidTankHandler; import brachy.modularui.value.sync.DoubleSyncValue; import brachy.modularui.value.sync.DynamicLinkedSyncHandler; import brachy.modularui.value.sync.DynamicSyncHandler; diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/fluid/CycleFluidEntryHandler.java b/src/main/java/brachy/modularui/utils/handlers/fluid/CycleFluidEntryHandler.java similarity index 97% rename from src/main/java/brachy/modularui/integration/recipeviewer/handlers/fluid/CycleFluidEntryHandler.java rename to src/main/java/brachy/modularui/utils/handlers/fluid/CycleFluidEntryHandler.java index a4909237..bf44e684 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/fluid/CycleFluidEntryHandler.java +++ b/src/main/java/brachy/modularui/utils/handlers/fluid/CycleFluidEntryHandler.java @@ -1,4 +1,4 @@ -package brachy.modularui.integration.recipeviewer.handlers.fluid; +package brachy.modularui.utils.handlers.fluid; import brachy.modularui.integration.recipeviewer.entry.fluid.FluidEntryList; diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/fluid/EmptyFluidTank.java b/src/main/java/brachy/modularui/utils/handlers/fluid/EmptyFluidTank.java similarity index 94% rename from src/main/java/brachy/modularui/integration/recipeviewer/handlers/fluid/EmptyFluidTank.java rename to src/main/java/brachy/modularui/utils/handlers/fluid/EmptyFluidTank.java index 8e42cc49..bd58258c 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/fluid/EmptyFluidTank.java +++ b/src/main/java/brachy/modularui/utils/handlers/fluid/EmptyFluidTank.java @@ -1,4 +1,4 @@ -package brachy.modularui.integration.recipeviewer.handlers.fluid; +package brachy.modularui.utils.handlers.fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidTank; diff --git a/src/main/java/brachy/modularui/utils/FluidTankHandler.java b/src/main/java/brachy/modularui/utils/handlers/fluid/FluidTankHandler.java old mode 100755 new mode 100644 similarity index 97% rename from src/main/java/brachy/modularui/utils/FluidTankHandler.java rename to src/main/java/brachy/modularui/utils/handlers/fluid/FluidTankHandler.java index 2f117a2e..f01dd5f6 --- a/src/main/java/brachy/modularui/utils/FluidTankHandler.java +++ b/src/main/java/brachy/modularui/utils/handlers/fluid/FluidTankHandler.java @@ -1,4 +1,4 @@ -package brachy.modularui.utils; +package brachy.modularui.utils.handlers.fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidTank; diff --git a/src/main/java/brachy/modularui/utils/IMultiFluidTankHandler.java b/src/main/java/brachy/modularui/utils/handlers/fluid/IMultiFluidTankHandler.java similarity index 81% rename from src/main/java/brachy/modularui/utils/IMultiFluidTankHandler.java rename to src/main/java/brachy/modularui/utils/handlers/fluid/IMultiFluidTankHandler.java index b65778d0..e99fe55c 100644 --- a/src/main/java/brachy/modularui/utils/IMultiFluidTankHandler.java +++ b/src/main/java/brachy/modularui/utils/handlers/fluid/IMultiFluidTankHandler.java @@ -1,4 +1,4 @@ -package brachy.modularui.utils; +package brachy.modularui.utils.handlers.fluid; import net.minecraftforge.fluids.IFluidTank; import net.minecraftforge.fluids.capability.IFluidHandler; diff --git a/src/main/java/brachy/modularui/utils/MultiFluidTankHandler.java b/src/main/java/brachy/modularui/utils/handlers/fluid/MultiFluidTankHandler.java similarity index 98% rename from src/main/java/brachy/modularui/utils/MultiFluidTankHandler.java rename to src/main/java/brachy/modularui/utils/handlers/fluid/MultiFluidTankHandler.java index edc6614a..aaeef405 100644 --- a/src/main/java/brachy/modularui/utils/MultiFluidTankHandler.java +++ b/src/main/java/brachy/modularui/utils/handlers/fluid/MultiFluidTankHandler.java @@ -1,4 +1,4 @@ -package brachy.modularui.utils; +package brachy.modularui.utils.handlers.fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidTank; diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/item/CycleItemEntryHandler.java b/src/main/java/brachy/modularui/utils/handlers/item/CycleItemEntryHandler.java similarity index 97% rename from src/main/java/brachy/modularui/integration/recipeviewer/handlers/item/CycleItemEntryHandler.java rename to src/main/java/brachy/modularui/utils/handlers/item/CycleItemEntryHandler.java index 5b387bed..126febbd 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/handlers/item/CycleItemEntryHandler.java +++ b/src/main/java/brachy/modularui/utils/handlers/item/CycleItemEntryHandler.java @@ -1,4 +1,4 @@ -package brachy.modularui.integration.recipeviewer.handlers.item; +package brachy.modularui.utils.handlers.item; import brachy.modularui.integration.recipeviewer.entry.item.ItemEntryList; import brachy.modularui.integration.recipeviewer.entry.item.ItemStackList; diff --git a/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java b/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java index a405197d..dff28851 100644 --- a/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java +++ b/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java @@ -1,7 +1,7 @@ package brachy.modularui.value.sync; -import brachy.modularui.utils.FluidTankHandler; -import brachy.modularui.utils.IMultiFluidTankHandler; +import brachy.modularui.utils.handlers.fluid.FluidTankHandler; +import brachy.modularui.utils.handlers.fluid.IMultiFluidTankHandler; import brachy.modularui.utils.MouseData; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java index 68e8e9ff..65f1f164 100644 --- a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java +++ b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java @@ -12,7 +12,7 @@ import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.theme.SlotTheme; import brachy.modularui.theme.WidgetThemeEntry; -import brachy.modularui.utils.IMultiFluidTankHandler; +import brachy.modularui.utils.handlers.fluid.IMultiFluidTankHandler; import brachy.modularui.utils.MouseData; import brachy.modularui.value.sync.FluidSlotSyncHandler; import brachy.modularui.widgets.AbstractFluidDisplayWidget; From 91aa476a7f91c4db70a6f0fc19dd98bac514ec00 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:07:51 +0300 Subject: [PATCH 54/84] Rename IMultiFluidTankHandler to IMultiTankFluidHandler also did the same to the impl --- src/main/java/brachy/modularui/test/TestBlockEntity.java | 6 +++--- ...FluidTankHandler.java => IMultiTankFluidHandler.java} | 2 +- ...iFluidTankHandler.java => MultiTankFluidHandler.java} | 8 ++++---- .../modularui/value/sync/FluidSlotSyncHandler.java | 4 ++-- .../java/brachy/modularui/widgets/slot/FluidSlot.java | 9 +++++---- 5 files changed, 15 insertions(+), 14 deletions(-) rename src/main/java/brachy/modularui/utils/handlers/fluid/{IMultiFluidTankHandler.java => IMultiTankFluidHandler.java} (75%) rename src/main/java/brachy/modularui/utils/handlers/fluid/{MultiFluidTankHandler.java => MultiTankFluidHandler.java} (94%) diff --git a/src/main/java/brachy/modularui/test/TestBlockEntity.java b/src/main/java/brachy/modularui/test/TestBlockEntity.java index 716e6d26..346bed21 100644 --- a/src/main/java/brachy/modularui/test/TestBlockEntity.java +++ b/src/main/java/brachy/modularui/test/TestBlockEntity.java @@ -18,7 +18,7 @@ import brachy.modularui.utils.Alignment; import brachy.modularui.utils.Color; import brachy.modularui.utils.Interpolation; -import brachy.modularui.utils.handlers.fluid.MultiFluidTankHandler; +import brachy.modularui.utils.handlers.fluid.MultiTankFluidHandler; import brachy.modularui.value.sync.DoubleSyncValue; import brachy.modularui.value.sync.DynamicLinkedSyncHandler; import brachy.modularui.value.sync.DynamicSyncHandler; @@ -99,8 +99,8 @@ public int getSlotLimit(int slot) { } }; private final ItemStackHandler phantomStorage = new ItemStackHandler(3); - private final MultiFluidTankHandler fluidStorage = new MultiFluidTankHandler(3, 10000); - private final MultiFluidTankHandler phantomFluidStorage = new MultiFluidTankHandler(3, 500000); + private final MultiTankFluidHandler fluidStorage = new MultiTankFluidHandler(3, 10000); + private final MultiTankFluidHandler phantomFluidStorage = new MultiTankFluidHandler(3, 500000); private final ItemStackHandler craftingInventory = new ItemStackHandler(10); private final ItemStackHandler storageInventory0 = new ItemStackHandler(1); private final Map stackHandlerMap = new Object2ObjectOpenHashMap<>(); diff --git a/src/main/java/brachy/modularui/utils/handlers/fluid/IMultiFluidTankHandler.java b/src/main/java/brachy/modularui/utils/handlers/fluid/IMultiTankFluidHandler.java similarity index 75% rename from src/main/java/brachy/modularui/utils/handlers/fluid/IMultiFluidTankHandler.java rename to src/main/java/brachy/modularui/utils/handlers/fluid/IMultiTankFluidHandler.java index e99fe55c..8efe5b58 100644 --- a/src/main/java/brachy/modularui/utils/handlers/fluid/IMultiFluidTankHandler.java +++ b/src/main/java/brachy/modularui/utils/handlers/fluid/IMultiTankFluidHandler.java @@ -3,7 +3,7 @@ import net.minecraftforge.fluids.IFluidTank; import net.minecraftforge.fluids.capability.IFluidHandler; -public interface IMultiFluidTankHandler extends IFluidHandler { +public interface IMultiTankFluidHandler extends IFluidHandler { IFluidTank getFluidTank(int index); } diff --git a/src/main/java/brachy/modularui/utils/handlers/fluid/MultiFluidTankHandler.java b/src/main/java/brachy/modularui/utils/handlers/fluid/MultiTankFluidHandler.java similarity index 94% rename from src/main/java/brachy/modularui/utils/handlers/fluid/MultiFluidTankHandler.java rename to src/main/java/brachy/modularui/utils/handlers/fluid/MultiTankFluidHandler.java index aaeef405..37814497 100644 --- a/src/main/java/brachy/modularui/utils/handlers/fluid/MultiFluidTankHandler.java +++ b/src/main/java/brachy/modularui/utils/handlers/fluid/MultiTankFluidHandler.java @@ -10,11 +10,11 @@ import java.util.Objects; import java.util.function.IntFunction; -public class MultiFluidTankHandler implements IMultiFluidTankHandler { +public class MultiTankFluidHandler implements IMultiTankFluidHandler { private final IFluidTank[] tanks; - public MultiFluidTankHandler(IFluidTank... tanks) { + public MultiTankFluidHandler(IFluidTank... tanks) { Objects.requireNonNull(tanks); for (IFluidTank tank : tanks) { Objects.requireNonNull(tank); @@ -22,11 +22,11 @@ public MultiFluidTankHandler(IFluidTank... tanks) { this.tanks = Arrays.copyOf(tanks, tanks.length); } - public MultiFluidTankHandler(int count, int capacity) { + public MultiTankFluidHandler(int count, int capacity) { this(count, i -> new FluidTank(capacity)); } - public MultiFluidTankHandler(int count, IntFunction tankBuilder) { + public MultiTankFluidHandler(int count, IntFunction tankBuilder) { this.tanks = new IFluidTank[count]; for (int i = 0; i < count; i++) { this.tanks[i] = Objects.requireNonNull(tankBuilder.apply(i)); diff --git a/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java b/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java index dff28851..f7c7c4d7 100644 --- a/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java +++ b/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java @@ -1,7 +1,7 @@ package brachy.modularui.value.sync; import brachy.modularui.utils.handlers.fluid.FluidTankHandler; -import brachy.modularui.utils.handlers.fluid.IMultiFluidTankHandler; +import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; import brachy.modularui.utils.MouseData; import net.minecraft.network.FriendlyByteBuf; @@ -51,7 +51,7 @@ public FluidSlotSyncHandler(IFluidTank fluidTank) { allowC2S(); } - public FluidSlotSyncHandler(IMultiFluidTankHandler fluidTank, int index) { + public FluidSlotSyncHandler(IMultiTankFluidHandler fluidTank, int index) { this(fluidTank.getFluidTank(index)); } diff --git a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java index 65f1f164..0f69ef4a 100644 --- a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java +++ b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java @@ -12,7 +12,8 @@ import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.theme.SlotTheme; import brachy.modularui.theme.WidgetThemeEntry; -import brachy.modularui.utils.handlers.fluid.IMultiFluidTankHandler; +import brachy.modularui.utils.handlers.fluid.EmptyFluidTank; +import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; import brachy.modularui.utils.MouseData; import brachy.modularui.value.sync.FluidSlotSyncHandler; import brachy.modularui.widgets.AbstractFluidDisplayWidget; @@ -247,8 +248,8 @@ public FluidSlot syncHandler(IFluidTank fluidTank) { return syncHandler(new FluidSlotSyncHandler(fluidTank)); } - public FluidSlot syncHandler(IMultiFluidTankHandler fluidTank, int index) { - return syncHandler(fluidTank.getFluidTank(index)); + public FluidSlot syncHandler(IMultiTankFluidHandler fluidTank, int index) { + return syncHandler(new FluidSlotSyncHandler(fluidTank, index)); } public FluidSlot syncHandler(FluidSlotSyncHandler syncHandler) { @@ -260,7 +261,7 @@ public FluidSlot tank(IFluidTank fluidTank) { return syncHandler(fluidTank); } - public FluidSlot tank(IMultiFluidTankHandler fluidTank, int index) { + public FluidSlot tank(IMultiTankFluidHandler fluidTank, int index) { return syncHandler(fluidTank, index); } From a60ef1b26e0d389eca5cad0c90de68018795f1dc Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:11:56 +0300 Subject: [PATCH 55/84] Add fluid slots to test recipe(s) --- .../brachy/modularui/test/TestMachine.java | 195 +++++++++++++----- .../modularui/test/TestRecipeViewerGuis.java | 39 +++- .../fluid/CombinedFluidHandlerWrapper.java | 106 ++++++++++ .../value/sync/FluidSlotSyncHandler.java | 17 +- .../widgets/AbstractFluidDisplayWidget.java | 15 +- .../modularui/widgets/slot/FluidSlot.java | 4 +- 6 files changed, 303 insertions(+), 73 deletions(-) create mode 100644 src/main/java/brachy/modularui/utils/handlers/fluid/CombinedFluidHandlerWrapper.java diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index a77e34fe..24b77173 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -16,6 +16,9 @@ import brachy.modularui.screen.ModularScreen; import brachy.modularui.screen.UISettings; import brachy.modularui.utils.Color; +import brachy.modularui.utils.handlers.fluid.CombinedFluidHandlerWrapper; +import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; +import brachy.modularui.utils.handlers.fluid.MultiTankFluidHandler; import brachy.modularui.value.StringValue; import brachy.modularui.value.sync.BooleanSyncValue; import brachy.modularui.value.sync.DoubleSyncValue; @@ -28,6 +31,7 @@ import brachy.modularui.widgets.ToggleButton; import brachy.modularui.widgets.layout.Flow; import brachy.modularui.widgets.menu.DropdownWidget; +import brachy.modularui.widgets.slot.FluidSlot; import brachy.modularui.widgets.slot.ItemSlot; import brachy.modularui.widgets.slot.ModularSlot; @@ -42,9 +46,13 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidType; +import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandlerModifiable; import net.minecraftforge.items.ItemHandlerHelper; @@ -61,11 +69,16 @@ @ApiStatus.Experimental public class TestMachine { + private static final int FLUID_SLOT_CAPACITY = 10_000; + public static class BE extends AbstractBlockEntity implements IUIHolder { - private final ItemStackHandler input = new ItemStackHandler(4); - private final ItemStackHandler output = new ItemStackHandler(4); - private final LazyOptional inv = LazyOptional.of(() -> new CombinedInvWrapper(input, output)); + private final ItemStackHandler inputItems = new ItemStackHandler(4); + private final ItemStackHandler outputItems = new ItemStackHandler(4); + private final MultiTankFluidHandler inputFluids = new MultiTankFluidHandler(2, FLUID_SLOT_CAPACITY); + private final MultiTankFluidHandler outputFluids = new MultiTankFluidHandler(2, FLUID_SLOT_CAPACITY); + private final LazyOptional items = LazyOptional.of(() -> new CombinedInvWrapper(inputItems, outputItems)); + private final LazyOptional fluids = LazyOptional.of(() -> new CombinedFluidHandlerWrapper(inputFluids, outputFluids)); private int ticks = 0; private boolean running = false, paused = false; @@ -79,7 +92,9 @@ public BE(BlockPos pos, BlockState blockState) { @Override public @NotNull LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { if (cap == ForgeCapabilities.ITEM_HANDLER) { - return this.inv.cast(); + return this.items.cast(); + } else if (cap == ForgeCapabilities.FLUID_HANDLER) { + return this.fluids.cast(); } return super.getCapability(cap, side); } @@ -102,7 +117,7 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UI .coverChildren(176, 30) .padding(7) .widgetTheme(IThemeApi.PANEL) - .child(Recipes.buildMachineUI(panel, this.input, this.output, new DoubleSyncValue(this::getProgress), true)) + .child(Recipes.buildMachineUI(panel, this.inputItems, this.outputItems, this.inputFluids, this.outputFluids, new DoubleSyncValue(this::getProgress), true)) .child(new ParentWidget<>() .coverChildren() .decoration() @@ -137,21 +152,21 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UI public void update() { if (hasLevel() && !getLevel().isClientSide) { if (this.running && this.lastRecipe != null && !this.paused) { - if (++this.recipeProgress == this.lastRecipe.ticks) { - this.lastRecipe.finishRecipe(this.output); + if (++this.recipeProgress >= this.lastRecipe.ticks) { + this.lastRecipe.finishRecipe(this.outputItems, this.outputFluids); this.recipeProgress = 0; - if (this.lastRecipe.startRecipe(this.input, true)) { - this.lastRecipe.startRecipe(this.input, false); + if (this.lastRecipe.startRecipe(this.inputItems, this.inputFluids, true)) { + this.lastRecipe.startRecipe(this.inputItems, this.inputFluids, false); } else { this.running = false; } } } else if (this.ticks % 20 == 0) { - Recipe recipe = Recipes.findRecipe(this.input, this.lastRecipe); + Recipe recipe = Recipes.findRecipe(this.inputItems, this.inputFluids, this.lastRecipe); if (recipe != null) { this.lastRecipe = recipe; this.recipeProgress = 0; - this.lastRecipe.startRecipe(this.input, false); + this.lastRecipe.startRecipe(this.inputItems, this.inputFluids, false); this.running = true; } } @@ -168,37 +183,64 @@ public double getProgress() { public static class Recipe { protected final ResourceLocation id; - protected final List in = new ArrayList<>(), out = new ArrayList<>(); + protected final List inItems = new ArrayList<>(), outItems = new ArrayList<>(); + protected final List inFluids = new ArrayList<>(), outFluids = new ArrayList<>(); private int ticks = 80; public Recipe(String name) { this.id = ModularUI.id(name); } - public Recipe in(ItemStack stack) { - if (!stack.isEmpty()) this.in.add(stack); + public Recipe itemIn(ItemStack stack) { + if (!stack.isEmpty()) this.inItems.add(stack); + return this; + } + + public Recipe itemIn(Item item, int count) { + return itemIn(new ItemStack(item, count)); + } + + public Recipe itemIn(Item item) { + return itemIn(item, 1); + } + + public Recipe itemOut(ItemStack stack) { + if (!stack.isEmpty()) this.outItems.add(stack); return this; } - public Recipe in(Item item, int count) { - return in(new ItemStack(item, count)); + public Recipe itemOut(Item item, int count) { + return itemOut(new ItemStack(item, count)); } - public Recipe in(Item item) { - return in(item, 1); + public Recipe itemOut(Item item) { + return itemOut(item, 1); } - public Recipe out(ItemStack stack) { - if (!stack.isEmpty()) this.out.add(stack); + public Recipe fluidIn(FluidStack stack) { + if (!stack.isEmpty()) this.inFluids.add(stack); return this; } - public Recipe out(Item item, int count) { - return out(new ItemStack(item, count)); + public Recipe fluidIn(Fluid fluid, int count) { + return fluidIn(new FluidStack(fluid, count)); } - public Recipe out(Item item) { - return out(item, 1); + public Recipe fluidIn(Fluid fluid) { + return fluidIn(fluid, FluidType.BUCKET_VOLUME); + } + + public Recipe fluidOut(FluidStack stack) { + if (!stack.isEmpty()) this.outFluids.add(stack); + return this; + } + + public Recipe fluidOut(Fluid fluid, int count) { + return fluidOut(new FluidStack(fluid, count)); + } + + public Recipe fluidOut(Fluid fluid) { + return fluidOut(fluid, FluidType.BUCKET_VOLUME); } public Recipe ticks(int ticks) { @@ -206,18 +248,26 @@ public Recipe ticks(int ticks) { return this; } - public boolean startRecipe(IItemHandler handler, boolean simulate) { - for (ItemStack in : this.in) { - if (!Recipes.extract(in, handler, simulate)) { + public boolean startRecipe(IItemHandler itemHandler, IFluidHandler fluidHandler, boolean simulate) { + for (ItemStack in : this.inItems) { + if (!Recipes.extract(in, itemHandler, simulate)) { + return false; + } + } + for (FluidStack in : this.inFluids) { + if (!Recipes.extract(in, fluidHandler, simulate)) { return false; } } return true; } - public void finishRecipe(IItemHandler handler) { - for (ItemStack out : this.out) { - ItemHandlerHelper.insertItemStacked(handler, out, false); + public void finishRecipe(IItemHandler itemHandler, IFluidHandler fluidHandler) { + for (ItemStack out : this.outItems) { + ItemHandlerHelper.insertItemStacked(itemHandler, out, false); + } + for (FluidStack out : this.outFluids) { + fluidHandler.fill(out.copy(), IFluidHandler.FluidAction.EXECUTE); } } } @@ -226,10 +276,10 @@ public static class Recipes { public static final List list = Util.make(new ArrayList<>(), list -> { list.add(new Recipe("/stuff_to_nether_star") - .in(Items.DIAMOND) - .in(Items.EMERALD) - .in(Items.GOLD_INGOT, 4) - .out(Items.NETHER_STAR)); + .itemIn(Items.DIAMOND) + .itemIn(Items.EMERALD) + .itemIn(Items.GOLD_INGOT, 4) + .itemOut(Items.NETHER_STAR)); // add (many) more recipes with (consistent) random inputs and outputs to try to overflow the cache for testing final int VERY_MANY_RECIPES = 256; @@ -240,22 +290,39 @@ public static class Recipes { // start counting from 1 so we get exactly 256 recipes for (int i = 1; i < VERY_MANY_RECIPES; i++) { - int inAmount = Math.min(i % 4 + 1, 4); - int outAmount = Math.min((i + 3) % 4 + 1, 4); - Recipe recipe = new Recipe("/random_" + i + "_in-" + inAmount + "_out-" + outAmount); + int inItems = rng.nextIntBetweenInclusive(1, 4), inFluids = rng.nextIntBetweenInclusive(1, 4); + int outItems = rng.nextIntBetweenInclusive(1, 4), outFluids = rng.nextIntBetweenInclusive(1, 4); + Recipe recipe = new Recipe("/random_" + i) + .ticks(rng.nextIntBetweenInclusive(1, 512)); - while (recipe.in.size() < inAmount) { + while (recipe.inItems.size() < inItems) { BuiltInRegistries.ITEM.getRandom(rng).ifPresent(item -> { - ItemStack stack = new ItemStack(item); + ItemStack stack = item.value().getDefaultInstance(); stack.setCount(Mth.clamp((int) (rng.nextGaussian() * stack.getMaxStackSize()), 0, stack.getMaxStackSize())); - recipe.in(stack); + recipe.itemIn(stack); }); } - while (recipe.out.size() < outAmount) { + while (recipe.outItems.size() < outItems) { BuiltInRegistries.ITEM.getRandom(rng).ifPresent(item -> { - ItemStack stack = new ItemStack(item); + ItemStack stack = item.value().getDefaultInstance(); stack.setCount(Mth.clamp((int) (rng.nextGaussian() * stack.getMaxStackSize()), 0, stack.getMaxStackSize())); - recipe.out(stack); + recipe.itemOut(stack); + }); + } + while (recipe.inFluids.size() < inFluids) { + BuiltInRegistries.FLUID.getRandom(rng).ifPresent(fluid -> { + int amount = Mth.clamp((int) (rng.nextGaussian() * FLUID_SLOT_CAPACITY), 0, FLUID_SLOT_CAPACITY); + if (amount == 0) return; + + recipe.fluidIn(fluid.value(), amount); + }); + } + while (recipe.outFluids.size() < outFluids) { + BuiltInRegistries.FLUID.getRandom(rng).ifPresent(fluid -> { + int amount = Mth.clamp((int) (rng.nextGaussian() * FLUID_SLOT_CAPACITY), 0, FLUID_SLOT_CAPACITY); + if (amount == 0) return; + + recipe.fluidOut(fluid.value(), amount); }); } @@ -263,10 +330,10 @@ public static class Recipes { } }); - public static Recipe findRecipe(IItemHandler input, @Nullable Recipe lastRecipe) { - if (lastRecipe != null && lastRecipe.startRecipe(input, true)) return lastRecipe; + public static Recipe findRecipe(IItemHandler inputItems, IFluidHandler inputFluids, @Nullable Recipe lastRecipe) { + if (lastRecipe != null && lastRecipe.startRecipe(inputItems, inputFluids, true)) return lastRecipe; for (Recipe recipe : list) { - if (recipe.startRecipe(input, true)) { + if (recipe.startRecipe(inputItems, inputFluids, true)) { return recipe; } } @@ -291,7 +358,21 @@ private static boolean extract(ItemStack stack, IItemHandler handler, boolean si return false; } - public static IWidget buildMachineUI(ModularPanel panel, IItemHandler in, IItemHandler out, IDoubleValue progress, + private static boolean extract(FluidStack stack, IFluidHandler handler, boolean simulate) { + if (stack.isEmpty()) return true; + FluidStack c = handler.drain(stack.getAmount(), IFluidHandler.FluidAction.SIMULATE); + if (stack.isFluidEqual(c)) { + if (!simulate) { + c = handler.drain(stack.getAmount(), IFluidHandler.FluidAction.EXECUTE); + } + return c.getAmount() >= stack.getAmount(); + } + + return false; + } + + public static IWidget buildMachineUI(ModularPanel panel, IItemHandler inItems, IItemHandler outItems, + IMultiTankFluidHandler inFluids, IMultiTankFluidHandler outFluids, IDoubleValue progress, final boolean addRecipeViewerClickArea) { var val = new StringValue("Option 1"); IPanelHandler panelHandler = IPanelHandler.simple(panel, (parent, player) -> { @@ -318,9 +399,13 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler in, IIt .child(Flow.row().name("slots") .coverChildren() .childPadding(8) - .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() - .slot(new ModularSlot(in, i)) - .recipeRole(RecipeSlotRole.INPUT))) + .child(Flow.col().name("input") + .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() + .slot(new ModularSlot(inItems, i)) + .recipeRole(RecipeSlotRole.INPUT))) + .child(SlotGroupWidget.rect(2, 1, i -> new FluidSlot() + .tank(inFluids, i) + .recipeRole(RecipeSlotRole.INPUT)))) .child(new ProgressWidget() .value(progress) .size(20) @@ -334,9 +419,13 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler in, IIt }); } })) - .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() - .slot(new ModularSlot(out, i).canPut(false)) - .recipeRole(RecipeSlotRole.OUTPUT)))); + .child(Flow.col().name("output") + .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() + .slot(new ModularSlot(outItems, i).canPut(false).canDragInto(false)) + .recipeRole(RecipeSlotRole.OUTPUT))) + .child(SlotGroupWidget.rect(2, 1, i -> new FluidSlot() + .tank(outFluids, i) + .recipeRole(RecipeSlotRole.OUTPUT))))); } } } diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index c89169f0..ad68f2de 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -3,6 +3,8 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidTank; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.wrapper.EmptyHandler; @@ -16,7 +18,11 @@ import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; import brachy.modularui.integration.rei.recipe.ModularUIREIDisplayCategory; import brachy.modularui.screen.ModularPanel; +import brachy.modularui.utils.handlers.fluid.EmptyFluidTank; +import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; +import brachy.modularui.utils.handlers.fluid.MultiTankFluidHandler; import brachy.modularui.value.DoubleValue; +import brachy.modularui.widgets.slot.FluidSlot; import brachy.modularui.widgets.slot.ItemSlot; import dev.emi.emi.api.EmiApi; @@ -56,20 +62,37 @@ public int getSlots() { return Integer.MAX_VALUE; // pls don't iterate UwU } }; + private static final IMultiTankFluidHandler EMPTY_MULTI_TANK_FLUID_HANDLER = new MultiTankFluidHandler() { + @Override + public int getTanks() { + return Integer.MAX_VALUE; // pls don't iterate UwU + } + + @Override + public IFluidTank getFluidTank(int index) { + return EmptyFluidTank.INSTANCE; + } + }; public static final Component CATEGORY_TITLE = Component.translatable("recipe_category.modularui.machine"); public static IWidget buildViewerUI(TestMachine.Recipe recipe) { var panel = new ModularPanel<>("recipe_viewer_recipe") .coverChildren(60, 40) .invisible(); - IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_INFINITE_ITEM_HANDLER, DoubleValue.simulateProgress(5000), false); + IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_MULTI_TANK_FLUID_HANDLER, EMPTY_MULTI_TANK_FLUID_HANDLER, DoubleValue.simulateProgress(5000), false); recipeUI.visitTransformAllChildren(w -> { if (w instanceof ItemSlot slot) { - List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.in : recipe.out; + List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.inItems : recipe.outItems; int index = slot.getSlot().getSlotIndex(); ItemStack item = index >= l.size() ? ItemStack.EMPTY : l.get(index); return slot.toRecipeViewerSlot() .value(item); + } else if (w instanceof FluidSlot slot) { + List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.inFluids : recipe.outFluids; + int index = slot.getSyncHandler().getTankIndex(); + FluidStack fluid = index < 0 || index >= l.size() ? FluidStack.EMPTY : l.get(index); + return slot.toRecipeViewerSlot() + .value(fluid); } else if (w instanceof IngredientProvider slot) { return slot.toRecipeViewerSlot(); } @@ -112,8 +135,8 @@ public static class RecipeDisplay extends ModularUIEmiRecipe { public RecipeDisplay(Supplier widgetSupplier, TestMachine.Recipe recipe) { super(recipe.id, widgetSupplier); this.recipe = recipe; - recipe.in.stream().map(EmiStack::of).forEach(inputs::add); - recipe.out.stream().map(EmiStack::of).forEach(outputs::add); + recipe.inItems.stream().map(EmiStack::of).forEach(inputs::add); + recipe.outItems.stream().map(EmiStack::of).forEach(outputs::add); calculateSize(); } @@ -176,8 +199,8 @@ public int getMaxHeight() { @Override public void setupRecipeIngredients(IRecipeLayoutBuilder builder, TestMachine.Recipe recipe, IFocusGroup focuses) { super.setupRecipeIngredients(builder, recipe, focuses); - recipe.in.forEach(item -> builder.addSlot(RecipeIngredientRole.INPUT).addItemStack(item)); - recipe.out.forEach(item -> builder.addSlot(RecipeIngredientRole.OUTPUT).addItemStack(item)); + recipe.inItems.forEach(item -> builder.addSlot(RecipeIngredientRole.INPUT).addItemStack(item)); + recipe.outItems.forEach(item -> builder.addSlot(RecipeIngredientRole.OUTPUT).addItemStack(item)); } @Override @@ -215,8 +238,8 @@ public static class RecipeDisplay extends ModularUIREIDisplay { public RecipeDisplay(Supplier recipeUI, TestMachine.Recipe recipe) { super(recipe.id, recipeUI, CATEGORY); this.recipe = recipe; - recipe.in.stream().map(EntryIngredients::of).forEach(inputEntries::add); - recipe.out.stream().map(EntryIngredients::of).forEach(outputEntries::add); + recipe.inItems.stream().map(EntryIngredients::of).forEach(inputEntries::add); + recipe.outItems.stream().map(EntryIngredients::of).forEach(outputEntries::add); calculateSize(); } diff --git a/src/main/java/brachy/modularui/utils/handlers/fluid/CombinedFluidHandlerWrapper.java b/src/main/java/brachy/modularui/utils/handlers/fluid/CombinedFluidHandlerWrapper.java new file mode 100644 index 00000000..8267ac8e --- /dev/null +++ b/src/main/java/brachy/modularui/utils/handlers/fluid/CombinedFluidHandlerWrapper.java @@ -0,0 +1,106 @@ +package brachy.modularui.utils.handlers.fluid; + +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.IFluidHandler; + +import org.jetbrains.annotations.NotNull; + +public class CombinedFluidHandlerWrapper implements IFluidHandler { + + private final IFluidHandler[] handlers; + + public CombinedFluidHandlerWrapper(IFluidHandler... handlers) { + this.handlers = handlers; + } + + @Override + public int getTanks() { + int tanks = 0; + for (IFluidHandler handler : handlers) { + tanks += handler.getTanks(); + } + return tanks; + } + + @Override + public @NotNull FluidStack getFluidInTank(int tank) { + int index = 0; + for (IFluidHandler handler : handlers) { + if (tank - index < handler.getTanks()) { + return handler.getFluidInTank(tank - index); + } + index += handler.getTanks(); + } + return FluidStack.EMPTY; + } + + @Override + public int getTankCapacity(int tank) { + int index = 0; + for (IFluidHandler handler : handlers) { + if (tank - index < handler.getTanks()) { + return handler.getTankCapacity(tank - index); + } + index += handler.getTanks(); + } + return 0; + } + + @Override + public boolean isFluidValid(int tank, @NotNull FluidStack stack) { + int index = 0; + for (IFluidHandler handler : handlers) { + if (tank - index < handler.getTanks()) { + return handler.isFluidValid(tank - index, stack); + } + index += handler.getTanks(); + } + return false; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) return 0; + var copied = resource.copy(); + for (IFluidHandler handler : handlers) { + var candidate = copied.copy(); + copied.shrink(handler.fill(candidate, action)); + if (copied.isEmpty()) break; + } + return resource.getAmount() - copied.getAmount(); + } + + @Override + public @NotNull FluidStack drain(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) return FluidStack.EMPTY; + var copied = resource.copy(); + for (IFluidHandler handler : handlers) { + var candidate = copied.copy(); + copied.shrink(handler.drain(candidate, action).getAmount()); + if (copied.isEmpty()) break; + } + copied.setAmount(resource.getAmount() - copied.getAmount()); + return copied; + } + + @Override + public @NotNull FluidStack drain(int maxDrain, FluidAction action) { + if (maxDrain == 0) return FluidStack.EMPTY; + FluidStack totalDrained = null; + for (IFluidHandler handler : handlers) { + if (totalDrained == null || totalDrained.isEmpty()) { + totalDrained = handler.drain(maxDrain, action); + if (totalDrained.isEmpty()) totalDrained = null; + else maxDrain -= totalDrained.getAmount(); + } else { + FluidStack copy = totalDrained.copy(); + copy.setAmount(maxDrain); + FluidStack drain = handler.drain(copy, action); + totalDrained.grow(drain.getAmount()); + maxDrain -= drain.getAmount(); + } + if (maxDrain <= 0) break; + } + return totalDrained == null ? FluidStack.EMPTY : totalDrained; + } +} diff --git a/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java b/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java index f7c7c4d7..ea8ebdea 100644 --- a/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java +++ b/src/main/java/brachy/modularui/value/sync/FluidSlotSyncHandler.java @@ -29,6 +29,8 @@ @Accessors(fluent = true, chain = true) public class FluidSlotSyncHandler extends ValueSyncHandler { + public static final int UNKNOWN_TANK_INDEX = -1; + public static final int SYNC_CLICK = 1; public static final int SYNC_SCROLL = 2; public static final int SYNC_CONTROLS_AMOUNT = 3; @@ -37,6 +39,13 @@ public class FluidSlotSyncHandler extends ValueSyncHandler + * Note that if this FluidSlotSyncHandler was constructed with only a fluid tank and no fluid handler, this field's value will be + * {@link #UNKNOWN_TANK_INDEX} so you should check that instead of assuming it's nonnegative (>=0). + */ + @Accessors(fluent = false) + @Getter private final int tankIndex; @Getter @Setter private boolean canFillSlot = true, canDrainSlot = true, phantom = false; @@ -48,11 +57,15 @@ public class FluidSlotSyncHandler extends ValueSyncHandler ingredientClass() { return FluidStack.class; } - public EntryList getIngredients() { - return FluidStackList.of(getFluidStack()); + public W recipeRole(RecipeSlotRole recipeRole) { + this.recipeRole = recipeRole; + return getThis(); } } diff --git a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java index 0f69ef4a..fe392a0d 100644 --- a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java +++ b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java @@ -30,7 +30,6 @@ import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidTank; -import net.minecraftforge.fluids.capability.templates.FluidTank; import net.minecraftforge.fml.ModList; import lombok.Getter; @@ -46,7 +45,6 @@ public class FluidSlot extends AbstractFluidDisplayWidget public static final String UNIT_BUCKET = "B"; public static final String UNIT_LITER = "L"; private static final DecimalFormat TOOLTIP_FORMAT = new DecimalFormat("#.##"); - private static final IFluidTank EMPTY = new FluidTank(0); static { TOOLTIP_FORMAT.setGroupingUsed(true); @@ -233,7 +231,7 @@ public FluidStack getFluidStack() { } public IFluidTank getFluidTank() { - return this.syncHandler == null ? EMPTY : this.syncHandler.fluidTank(); + return this.syncHandler == null ? EmptyFluidTank.INSTANCE : this.syncHandler.fluidTank(); } /** From 595825a5d3ef310820a09c902ab55679c91e1919 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:27:11 +0300 Subject: [PATCH 56/84] shorter line --- .../brachy/modularui/test/TestMachine.java | 32 +++++++++++++++---- .../modularui/test/TestRecipeViewerGuis.java | 19 +---------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index 24b77173..e5addf0e 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -22,6 +22,7 @@ import brachy.modularui.value.StringValue; import brachy.modularui.value.sync.BooleanSyncValue; import brachy.modularui.value.sync.DoubleSyncValue; +import brachy.modularui.value.sync.FluidSlotSyncHandler; import brachy.modularui.value.sync.PanelSyncManager; import brachy.modularui.widget.ParentWidget; import brachy.modularui.widget.SingleChildWidget; @@ -117,7 +118,7 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UI .coverChildren(176, 30) .padding(7) .widgetTheme(IThemeApi.PANEL) - .child(Recipes.buildMachineUI(panel, this.inputItems, this.outputItems, this.inputFluids, this.outputFluids, new DoubleSyncValue(this::getProgress), true)) + .child(Recipes.buildMachineUI(panel, this.inputItems, this.outputItems, this.inputFluids, this.outputFluids, new DoubleSyncValue(this::getProgress), false)) .child(new ParentWidget<>() .coverChildren() .decoration() @@ -373,7 +374,7 @@ private static boolean extract(FluidStack stack, IFluidHandler handler, boolean public static IWidget buildMachineUI(ModularPanel panel, IItemHandler inItems, IItemHandler outItems, IMultiTankFluidHandler inFluids, IMultiTankFluidHandler outFluids, IDoubleValue progress, - final boolean addRecipeViewerClickArea) { + final boolean isRecipeViewerUI) { var val = new StringValue("Option 1"); IPanelHandler panelHandler = IPanelHandler.simple(panel, (parent, player) -> { return new ModularPanel<>("test_sub_panel").size(50).overlay(Text.str("Test")); @@ -400,18 +401,27 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler inItems .coverChildren() .childPadding(8) .child(Flow.col().name("input") + .coverChildren() .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() - .slot(new ModularSlot(inItems, i)) + .configure(w -> { + if (!isRecipeViewerUI) { + w.slot(new ModularSlot(inItems, i)); + } + }) .recipeRole(RecipeSlotRole.INPUT))) .child(SlotGroupWidget.rect(2, 1, i -> new FluidSlot() - .tank(inFluids, i) + .configure(w -> { + if (!isRecipeViewerUI) { + w.syncHandler(new FluidSlotSyncHandler(inFluids, i)); + } + }) .recipeRole(RecipeSlotRole.INPUT)))) .child(new ProgressWidget() .value(progress) .size(20) .texture(GuiTextures.PROGRESS_ARROW, ProgressDrawable.Direction.RIGHT) .configure(w -> { - if (addRecipeViewerClickArea) { + if (!isRecipeViewerUI) { w.listenGuiAction((IGuiAction.MouseReleased) (ctx, button) -> { if (!ctx.isMouseAbove(w)) return false; TestRecipeViewerGuis.openTestRecipeViewerCategory(); @@ -420,10 +430,20 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler inItems } })) .child(Flow.col().name("output") + .coverChildren() .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() - .slot(new ModularSlot(outItems, i).canPut(false).canDragInto(false)) + .configure(w -> { + if (!isRecipeViewerUI) { + w.slot(new ModularSlot(outItems, i).canPut(false).canDragInto(false)); + } + }) .recipeRole(RecipeSlotRole.OUTPUT))) .child(SlotGroupWidget.rect(2, 1, i -> new FluidSlot() + .configure(w -> { + if (!isRecipeViewerUI) { + w.syncHandler(new FluidSlotSyncHandler(outFluids, i).canFillSlot(false)); + } + }) .tank(outFluids, i) .recipeRole(RecipeSlotRole.OUTPUT))))); } diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index ad68f2de..1ce4a448 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -56,30 +56,13 @@ public class TestRecipeViewerGuis { - private static final IItemHandler EMPTY_INFINITE_ITEM_HANDLER = new EmptyHandler() { - @Override - public int getSlots() { - return Integer.MAX_VALUE; // pls don't iterate UwU - } - }; - private static final IMultiTankFluidHandler EMPTY_MULTI_TANK_FLUID_HANDLER = new MultiTankFluidHandler() { - @Override - public int getTanks() { - return Integer.MAX_VALUE; // pls don't iterate UwU - } - - @Override - public IFluidTank getFluidTank(int index) { - return EmptyFluidTank.INSTANCE; - } - }; public static final Component CATEGORY_TITLE = Component.translatable("recipe_category.modularui.machine"); public static IWidget buildViewerUI(TestMachine.Recipe recipe) { var panel = new ModularPanel<>("recipe_viewer_recipe") .coverChildren(60, 40) .invisible(); - IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_INFINITE_ITEM_HANDLER, EMPTY_MULTI_TANK_FLUID_HANDLER, EMPTY_MULTI_TANK_FLUID_HANDLER, DoubleValue.simulateProgress(5000), false); + IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, null, null, null, null, DoubleValue.simulateProgress(5000), true); recipeUI.visitTransformAllChildren(w -> { if (w instanceof ItemSlot slot) { List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.inItems : recipe.outItems; From 74c2b515890dd0a10242cfcac59b12b6d25d135d Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 8 Aug 2026 09:05:38 +0300 Subject: [PATCH 57/84] Make the casing of recipeviewer compat classes consistent --- .../core/mixins/jei/RecipeGuiLogicMixin.java | 4 ++-- .../mixins/jei/RecipeLayoutBuilderMixin.java | 6 +++--- .../core/mixins/jei/RecipeLayoutMixin.java | 6 +++--- .../rei/DefaultDisplayViewingScreenMixin.java | 8 ++++---- .../integration/jei/JeiRecipeViewerSlot.java | 1 - ...Category.java => ModularUIJeiCategory.java} | 15 ++++++++------- ...IREIPlugin.java => ModularUIReiPlugin.java} | 6 +++--- ...Category.java => ModularUIReiCategory.java} | 4 ++-- ...EIDisplay.java => ModularUIReiDisplay.java} | 18 +++++++++--------- .../modularui/test/TestRecipeViewerGuis.java | 13 ++++++------- 10 files changed, 40 insertions(+), 41 deletions(-) rename src/main/java/brachy/modularui/integration/jei/recipe/{ModularUIRecipeCategory.java => ModularUIJeiCategory.java} (95%) rename src/main/java/brachy/modularui/integration/rei/{ModularUIREIPlugin.java => ModularUIReiPlugin.java} (91%) rename src/main/java/brachy/modularui/integration/rei/recipe/{ModularUIREIDisplayCategory.java => ModularUIReiCategory.java} (87%) rename src/main/java/brachy/modularui/integration/rei/recipe/{ModularUIREIDisplay.java => ModularUIReiDisplay.java} (95%) diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeGuiLogicMixin.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeGuiLogicMixin.java index fc8e0015..268e3cc4 100644 --- a/src/main/java/brachy/modularui/core/mixins/jei/RecipeGuiLogicMixin.java +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeGuiLogicMixin.java @@ -1,6 +1,6 @@ package brachy.modularui.core.mixins.jei; -import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; +import brachy.modularui.integration.jei.recipe.ModularUIJeiCategory; import com.llamalad7.mixinextras.sugar.Local; import mezz.jei.api.recipe.category.IRecipeCategory; @@ -17,7 +17,7 @@ public class RecipeGuiLogicMixin { @Local(name = "recipeCategory") IRecipeCategory recipeCategory) { // if this is a MUI category, force the gui to use the category's max height as the recipe height // (instead of the height of the first recipe it finds, which may be shorter than others) - if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + if (recipeCategory instanceof ModularUIJeiCategory muiCategory) { return muiCategory.getMaxHeight(); } return recipeHeight; diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderMixin.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderMixin.java index e299bb4d..d296fce1 100644 --- a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderMixin.java +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutBuilderMixin.java @@ -1,6 +1,6 @@ package brachy.modularui.core.mixins.jei; -import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; +import brachy.modularui.integration.jei.recipe.ModularUIJeiCategory; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; @@ -23,7 +23,7 @@ public abstract class RecipeLayoutBuilderMixin implements IRecipeLayoutBuilde @ModifyExpressionValue(method = { "createShapelessIcon", "getRecipeTransferButtonPosition" }, at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getWidth()I"), require = 0) private int modularui$makeWidthRecipeDependent(int original) { - if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + if (recipeCategory instanceof ModularUIJeiCategory muiCategory) { return muiCategory.getWidth(this.recipe); } return original; @@ -32,7 +32,7 @@ public abstract class RecipeLayoutBuilderMixin implements IRecipeLayoutBuilde @ModifyExpressionValue(method = { "createShapelessIcon", "getRecipeTransferButtonPosition" }, at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getHeight()I"), require = 0) private int modularui$makeHeightRecipeDependent(int original) { - if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + if (recipeCategory instanceof ModularUIJeiCategory muiCategory) { return muiCategory.getHeight(recipe); } return original; diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java index c172223e..f2753977 100644 --- a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java @@ -1,6 +1,6 @@ package brachy.modularui.core.mixins.jei; -import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; +import brachy.modularui.integration.jei.recipe.ModularUIJeiCategory; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; @@ -17,7 +17,7 @@ public abstract class RecipeLayoutMixin implements IRecipeLayoutDrawable { private int modularui$makeWidthRecipeDependent(int original, @Local(argsOnly = true) IRecipeCategory recipeCategory, @Local(argsOnly = true) R recipe) { - if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + if (recipeCategory instanceof ModularUIJeiCategory muiCategory) { return muiCategory.getWidth(recipe); } return original; @@ -27,7 +27,7 @@ public abstract class RecipeLayoutMixin implements IRecipeLayoutDrawable { private int modularui$makeHeightRecipeDependent(int original, @Local(argsOnly = true) IRecipeCategory recipeCategory, @Local(argsOnly = true) R recipe) { - if (recipeCategory instanceof ModularUIRecipeCategory muiCategory) { + if (recipeCategory instanceof ModularUIJeiCategory muiCategory) { return muiCategory.getHeight(recipe); } return original; diff --git a/src/main/java/brachy/modularui/core/mixins/rei/DefaultDisplayViewingScreenMixin.java b/src/main/java/brachy/modularui/core/mixins/rei/DefaultDisplayViewingScreenMixin.java index c40c5c86..b684d8a7 100644 --- a/src/main/java/brachy/modularui/core/mixins/rei/DefaultDisplayViewingScreenMixin.java +++ b/src/main/java/brachy/modularui/core/mixins/rei/DefaultDisplayViewingScreenMixin.java @@ -1,7 +1,7 @@ package brachy.modularui.core.mixins.rei; -import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; -import brachy.modularui.integration.rei.recipe.ModularUIREIDisplayCategory; +import brachy.modularui.integration.rei.recipe.ModularUIReiDisplay; +import brachy.modularui.integration.rei.recipe.ModularUIReiCategory; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; @@ -53,8 +53,8 @@ protected DefaultDisplayViewingScreenMixin(Map, List category, Display display, @Nullable ToIntFunction> originalValue) { - if (category instanceof ModularUIREIDisplayCategory muiCategory && display instanceof ModularUIREIDisplay muiDisplay) { - return ((ModularUIREIDisplayCategory) muiCategory).getDisplayHeight(muiDisplay); + if (category instanceof ModularUIReiCategory muiCategory && display instanceof ModularUIReiDisplay muiDisplay) { + return ((ModularUIReiCategory) muiCategory).getDisplayHeight(muiDisplay); } else { // if this isn't a MUI display, set it back to the original value // (I think it's possible that an MUI category has a non-MUI display in it? not sure tho.) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 1a03a49a..eaa58d14 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -29,7 +29,6 @@ import mezz.jei.library.ingredients.DisplayIngredientAcceptor; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java similarity index 95% rename from src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java rename to src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java index e2a6f34d..7716d6ad 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIRecipeCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java @@ -43,7 +43,7 @@ import java.util.function.Function; @ApiStatus.Experimental -public abstract class ModularUIRecipeCategory implements IRecipeCategory { +public abstract class ModularUIJeiCategory implements IRecipeCategory { public static final String SCREEN_NAME_PREFIX = "jei_recipe_"; @@ -55,7 +55,7 @@ public abstract class ModularUIRecipeCategory implements IRecipeCategory { private final Function recipeUI; private final Function recipeIdGetter; - protected ModularUIRecipeCategory(Function recipeUI, Function recipeIdGetter) { + protected ModularUIJeiCategory(Function recipeUI, Function recipeIdGetter) { this.recipeUI = recipeUI; this.recipeIdGetter = recipeIdGetter; @@ -66,9 +66,9 @@ protected ModularUIRecipeCategory(Function recipeUI, Function reload(T key, ModularScreen oldValue) { .build(new CacheLoader<>() { @Override public Dimensions load(T recipe) { - return ModularUIRecipeCategory.this.calculateSize(recipe); + return ModularUIJeiCategory.this.calculateSize(recipe); } }); } @@ -136,8 +136,9 @@ protected final void calculateAndCacheSize(T recipe) { public abstract int getMaxHeight(); /** - * Add your recipes' inputs and outputs here. This is used by JEI for lookups to figure out what ingredients are inputs and - * outputs for a recipe. + * Sets all the recipe's ingredients by filling out an instance of {@link IRecipeLayoutBuilder}. + * This is used by JEI for lookups, to figure out what ingredients are inputs and outputs for a recipe. + * *

* Note that you can only add inputs and outputs for JEI's recipe lookup/search here, as the layout builder that's * passed into this method only handles those and not the displayed recipe previews. diff --git a/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java b/src/main/java/brachy/modularui/integration/rei/ModularUIReiPlugin.java similarity index 91% rename from src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java rename to src/main/java/brachy/modularui/integration/rei/ModularUIReiPlugin.java index 42ef2726..68c64c65 100644 --- a/src/main/java/brachy/modularui/integration/rei/ModularUIREIPlugin.java +++ b/src/main/java/brachy/modularui/integration/rei/ModularUIReiPlugin.java @@ -2,7 +2,7 @@ import brachy.modularui.ModularUI; import brachy.modularui.integration.rei.handler.REIScreenHandler; -import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; +import brachy.modularui.integration.rei.recipe.ModularUIReiDisplay; import brachy.modularui.screen.ContainerScreenWrapper; import brachy.modularui.screen.ScreenWrapper; @@ -17,13 +17,13 @@ import me.shedaniel.rei.forge.REIPluginClient; @REIPluginClient -public class ModularUIREIPlugin implements REIClientPlugin { +public class ModularUIReiPlugin implements REIClientPlugin { /// This is only used to clear the stale recipe display "screen" cache. @SuppressWarnings("UnstableApiUsage") @Override public void registerEntryRenderers(EntryRendererRegistry registry) { - ModularUIREIDisplay.clearScreenCache(); + ModularUIReiDisplay.clearScreenCache(); } @Override diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiCategory.java similarity index 87% rename from src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java rename to src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiCategory.java index e438c993..a8ec9db0 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplayCategory.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiCategory.java @@ -8,7 +8,7 @@ import java.util.List; @ApiStatus.Experimental -public abstract class ModularUIREIDisplayCategory implements DisplayCategory { +public abstract class ModularUIReiCategory implements DisplayCategory { @Override public List setupDisplay(D display, Rectangle bounds) { @@ -38,7 +38,7 @@ public int getDisplayHeight(D display) { /** * Return the maximum expected display height here.
- * You should also return a per-category display height that's at most this value in {@link #getDisplayHeight(ModularUIREIDisplay)}. + * You should also return a per-category display height that's at most this value in {@link #getDisplayHeight(ModularUIReiDisplay)}. * @return The maximum expected display height */ public abstract int getMaxDisplayHeight(); diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java similarity index 95% rename from src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java rename to src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java index 9d9c5396..03e6f6d0 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIREIDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java @@ -6,7 +6,7 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; -import brachy.modularui.integration.rei.ModularUIREIPlugin; +import brachy.modularui.integration.rei.ModularUIReiPlugin; import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -42,26 +42,26 @@ import java.util.function.Supplier; @ApiStatus.Experimental -public abstract class ModularUIREIDisplay implements Display { +public abstract class ModularUIReiDisplay implements Display { private static final String SCREEN_NAME_PREFIX = "rei_display_"; - private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() + private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() .initialCapacity(64) .softValues() .build(new CacheLoader<>() { @Override - public ModularScreen load(ModularUIREIDisplay key) { + public ModularScreen load(ModularUIReiDisplay key) { return key.createScreen(); } @Override - public ListenableFuture reload(ModularUIREIDisplay key, ModularScreen oldValue) { + public ListenableFuture reload(ModularUIReiDisplay key, ModularScreen oldValue) { // if an old value is (somehow) available, reuse it return Futures.immediateFuture(oldValue); } }); - private static ModularScreen getModularScreen(ModularUIREIDisplay display) { + private static ModularScreen getModularScreen(ModularUIReiDisplay display) { return SCREEN_CACHE.getUnchecked(display); } @@ -72,7 +72,7 @@ private static ModularScreen getModularScreen(ModularUIREIDisplay display) { private boolean sizeCalculated = false; private int displayWidth, displayHeight; - public ModularUIREIDisplay(ResourceLocation recipeId, Supplier recipeUI, CategoryIdentifier categoryId) { + public ModularUIReiDisplay(ResourceLocation recipeId, Supplier recipeUI, CategoryIdentifier categoryId) { this.recipeId = recipeId; this.recipeUI = recipeUI; this.categoryIdentifier = categoryId; @@ -167,7 +167,7 @@ public static class UIWrapperWidget extends Widget { private final ModularUIREIDisplay display; private final float offsetX, offsetY; - public UIWrapperWidget(ModularUIREIDisplay display, float offsetX, float offsetY) { + public UIWrapperWidget(ModularUIReiDisplay display, float offsetX, float offsetY) { this.display = display; this.offsetX = offsetX; this.offsetY = offsetY; @@ -261,7 +261,7 @@ public boolean charTyped(char codePoint, int modifiers) { @ApiStatus.Internal public static void clearScreenCache() { - if (!BETTER_CALL_SAUL.getCallerClass().equals(ModularUIREIPlugin.class)) { + if (!BETTER_CALL_SAUL.getCallerClass().equals(ModularUIReiPlugin.class)) { throw new IllegalCallerException("Attempted to call ModularUIREIDisplay#clearScreenCache!"); } SCREEN_CACHE.invalidateAll(); diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index 1ce4a448..ee357bcf 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -12,11 +12,11 @@ import brachy.modularui.api.widget.IWidget; import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; import brachy.modularui.integration.jei.ModularUIJeiPlugin; -import brachy.modularui.integration.jei.recipe.ModularUIRecipeCategory; +import brachy.modularui.integration.jei.recipe.ModularUIJeiCategory; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.handlers.IngredientProvider; -import brachy.modularui.integration.rei.recipe.ModularUIREIDisplay; -import brachy.modularui.integration.rei.recipe.ModularUIREIDisplayCategory; +import brachy.modularui.integration.rei.recipe.ModularUIReiDisplay; +import brachy.modularui.integration.rei.recipe.ModularUIReiCategory; import brachy.modularui.screen.ModularPanel; import brachy.modularui.utils.handlers.fluid.EmptyFluidTank; import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; @@ -152,7 +152,7 @@ public static void registerRecipeCatalyst(IRecipeCatalystRegistration registry) } @NotNullByDefault - public static class RecipeCategory extends ModularUIRecipeCategory { + public static class RecipeCategory extends ModularUIJeiCategory { protected RecipeCategory(Function recipeUI, Function recipeIdGetter) { @@ -212,7 +212,7 @@ public static void registerDisplay(DisplayRegistry registry) { .forEach(registry::add); } - public static class RecipeDisplay extends ModularUIREIDisplay { + public static class RecipeDisplay extends ModularUIReiDisplay { private final TestMachine.Recipe recipe; @Getter private final List inputEntries = new ArrayList<>(); @@ -228,8 +228,7 @@ public RecipeDisplay(Supplier recipeUI, TestMachine.Recipe recipe) { } } - @NotNullByDefault - public static class RecipeCategory extends ModularUIREIDisplayCategory { + public static class RecipeCategory extends ModularUIReiCategory { protected RecipeCategory() { super(); From 543a9ab5d725fbccd9ddbd1e322b4505026d40dc Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 8 Aug 2026 10:57:19 +0300 Subject: [PATCH 58/84] Add generic category title support --- .../emi/recipe/ModularUIEmiCategory.java | 35 +++++++++++++++++++ .../jei/recipe/ModularUIJeiCategory.java | 7 ++++ .../recipeviewer/RecipeViewerUtils.java | 16 +++++++++ .../rei/recipe/ModularUIReiCategory.java | 9 +++++ .../modularui/test/TestRecipeViewerGuis.java | 14 +------- 5 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiCategory.java create mode 100644 src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerUtils.java diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiCategory.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiCategory.java new file mode 100644 index 00000000..f04e9c18 --- /dev/null +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiCategory.java @@ -0,0 +1,35 @@ +package brachy.modularui.integration.emi.recipe; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import brachy.modularui.integration.recipeviewer.RecipeViewerUtils; + +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.api.recipe.EmiRecipeCategory; +import dev.emi.emi.api.render.EmiRenderable; + +import java.util.Comparator; + +public class ModularUIEmiCategory extends EmiRecipeCategory { + + public ModularUIEmiCategory(ResourceLocation id, EmiRenderable icon) { + super(id, icon); + } + + public ModularUIEmiCategory(ResourceLocation id, EmiRenderable icon, EmiRenderable simplified) { + super(id, icon, simplified); + } + + public ModularUIEmiCategory(ResourceLocation id, EmiRenderable icon, EmiRenderable simplified, Comparator sorter) { + super(id, icon, simplified, sorter); + } + + /** + * Override the default translation key to a more recipeviewer-agnostic one + */ + @Override + public Component getName() { + return RecipeViewerUtils.getCategoryTitle(this.getId()); + } +} diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java index 7716d6ad..f7faeb7e 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java @@ -8,6 +8,7 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.jei.JeiRecipeViewerSlot; import brachy.modularui.integration.jei.ModularUIJeiPlugin; +import brachy.modularui.integration.recipeviewer.RecipeViewerUtils; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -17,6 +18,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import com.google.common.cache.CacheBuilder; @@ -212,6 +214,11 @@ public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget w return recipeViewerSlot; } + @Override + public Component getTitle() { + return RecipeViewerUtils.getCategoryTitle(this.getRecipeType().getUid()); + } + @Override public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { // guard against JEMI issues by explicitly checking for JEI's implementation diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerUtils.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerUtils.java new file mode 100644 index 00000000..21ffe030 --- /dev/null +++ b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerUtils.java @@ -0,0 +1,16 @@ +package brachy.modularui.integration.recipeviewer; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +public final class RecipeViewerUtils { + + private RecipeViewerUtils() {} + + public static final String CATEGORY_LANGUAGE_KEY_PREFIX = "gui.recipeviewer.category"; + + public static Component getCategoryTitle(ResourceLocation categoryId) { + return Component.translatable(categoryId.toLanguageKey(CATEGORY_LANGUAGE_KEY_PREFIX)); + } + +} diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiCategory.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiCategory.java index a8ec9db0..f4c4e837 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiCategory.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiCategory.java @@ -1,5 +1,9 @@ package brachy.modularui.integration.rei.recipe; +import net.minecraft.network.chat.Component; + +import brachy.modularui.integration.recipeviewer.RecipeViewerUtils; + import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.gui.widgets.Widget; import me.shedaniel.rei.api.client.registry.display.DisplayCategory; @@ -47,4 +51,9 @@ public int getDisplayHeight(D display) { public final int getDisplayHeight() { return getMaxDisplayHeight(); } + + @Override + public Component getTitle() { + return RecipeViewerUtils.getCategoryTitle(this.getIdentifier()); + } } diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index ee357bcf..8d3e25c6 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -54,10 +54,9 @@ import java.util.function.Function; import java.util.function.Supplier; +@NotNullByDefault public class TestRecipeViewerGuis { - public static final Component CATEGORY_TITLE = Component.translatable("recipe_category.modularui.machine"); - public static IWidget buildViewerUI(TestMachine.Recipe recipe) { var panel = new ModularPanel<>("recipe_viewer_recipe") .coverChildren(60, 40) @@ -151,7 +150,6 @@ public static void registerRecipeCatalyst(IRecipeCatalystRegistration registry) registry.addRecipeCatalyst(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get(), RECIPE_TYPE); } - @NotNullByDefault public static class RecipeCategory extends ModularUIJeiCategory { protected RecipeCategory(Function recipeUI, @@ -164,11 +162,6 @@ public RecipeType getRecipeType() { return RECIPE_TYPE; } - @Override - public Component getTitle() { - return CATEGORY_TITLE; - } - @Override public int getMaxWidth() { return 112; @@ -239,11 +232,6 @@ public CategoryIdentifier getCategoryIdentifier() { return CATEGORY; } - @Override - public Component getTitle() { - return CATEGORY_TITLE; - } - @Override public Renderer getIcon() { return EntryStacks.of(TestRegistration.TEST_MACHINE_BLOCK_ITEM.get()); From 41bd1f47a193b31150f4d8d226e845ba43dbf2ea Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:33:54 +0300 Subject: [PATCH 59/84] Store the embed screen in the previews' widgets and draw it from there instead of keeping a global cache Java garbage collection will take care of the rest, right? --- .../core/mixins/emi/EmiRenderHelperMixin.java | 25 ++++ .../integration/emi/ModularUIEmiPlugin.java | 8 -- .../emi/recipe/ModularUIEmiRecipe.java | 92 +++++++------- .../jei/recipe/ModularUIJeiCategory.java | 114 +++++++++--------- .../integration/rei/ModularUIReiPlugin.java | 9 -- .../rei/recipe/ModularUIReiDisplay.java | 59 ++------- src/main/resources/modularui.mixins.json | 1 + 7 files changed, 141 insertions(+), 167 deletions(-) create mode 100644 src/main/java/brachy/modularui/core/mixins/emi/EmiRenderHelperMixin.java diff --git a/src/main/java/brachy/modularui/core/mixins/emi/EmiRenderHelperMixin.java b/src/main/java/brachy/modularui/core/mixins/emi/EmiRenderHelperMixin.java new file mode 100644 index 00000000..d0cf4827 --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/emi/EmiRenderHelperMixin.java @@ -0,0 +1,25 @@ +package brachy.modularui.core.mixins.emi; + +import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; + +import dev.emi.emi.EmiRenderHelper; +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.runtime.EmiDrawContext; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = EmiRenderHelper.class, remap = false) +public class EmiRenderHelperMixin { + + @Inject(method = "renderRecipe", + at = @At(value = "INVOKE", target = "Ldev/emi/emi/api/recipe/EmiRecipe;addWidgets(Ldev/emi/emi/api/widget/WidgetHolder;)V")) + private static void modularui$tellTheRecipeItsInATooltip(EmiRecipe recipe, EmiDrawContext context, int x, int y, + boolean showMissing, int overlayColor, + CallbackInfo ci) { + if (recipe instanceof ModularUIEmiRecipe muiRecipe) { + muiRecipe.useScreenCacheForNextWidgetQuery(); + } + } +} diff --git a/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java b/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java index c51c7a7d..5df34a5c 100644 --- a/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java +++ b/src/main/java/brachy/modularui/integration/emi/ModularUIEmiPlugin.java @@ -2,26 +2,18 @@ import brachy.modularui.ModularUI; import brachy.modularui.integration.emi.handler.EmiScreenHandler; -import brachy.modularui.integration.emi.recipe.ModularUIEmiRecipe; import brachy.modularui.screen.ContainerScreenWrapper; import brachy.modularui.screen.ScreenWrapper; import brachy.modularui.test.TestRecipeViewerGuis; import dev.emi.emi.api.EmiEntrypoint; -import dev.emi.emi.api.EmiInitRegistry; import dev.emi.emi.api.EmiPlugin; import dev.emi.emi.api.EmiRegistry; @EmiEntrypoint public class ModularUIEmiPlugin implements EmiPlugin { - /// This is only used to clear the stale recipe display "screen" cache. - @Override - public void initialize(EmiInitRegistry registry) { - ModularUIEmiRecipe.clearScreenCache(); - } - @Override public void register(EmiRegistry registry) { EmiScreenHandler.register(ScreenWrapper.class, registry); diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 0d18b593..4cc63a7a 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -21,7 +21,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import dev.emi.emi.api.recipe.EmiRecipe; -import dev.emi.emi.api.stack.EmiIngredient; import dev.emi.emi.api.stack.EmiStack; import dev.emi.emi.api.widget.Bounds; import dev.emi.emi.api.widget.SlotWidget; @@ -31,7 +30,7 @@ import lombok.Getter; import org.jetbrains.annotations.ApiStatus; -import java.util.Iterator; +import java.time.Duration; import java.util.List; import java.util.function.Supplier; @@ -39,8 +38,10 @@ public abstract class ModularUIEmiRecipe implements EmiRecipe { private static final String SCREEN_NAME_PREFIX = "emi_recipe_"; - private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() - .initialCapacity(64) + private static final LoadingCache PREVIEW_SCREEN_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(Duration.ofSeconds(60)) + .initialCapacity(8) + .maximumSize(32) .softValues() .build(new CacheLoader<>() { @Override @@ -55,8 +56,8 @@ public ListenableFuture reload(ModularUIEmiRecipe key, ModularScr } }); - private static ModularScreen getModularScreen(ModularUIEmiRecipe recipe) { - return SCREEN_CACHE.getUnchecked(recipe); + private static ModularScreen getCachedModularScreen(ModularUIEmiRecipe recipe) { + return PREVIEW_SCREEN_CACHE.getUnchecked(recipe); } @Getter private final ResourceLocation id; @@ -131,9 +132,8 @@ public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { .invisible() .child(recipeUI); } - if (getInputs() != null && getOutputs() != null) { - panel = transform(panel); - } + panel = transform(panel); + ModularScreen screen = ModularScreen.createEmbed(owner, panel); screen.getContext().getUISettings().drawTooltipExternally(true); @@ -142,13 +142,11 @@ public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { } public ModularPanel transform(ModularPanel panel) { - Iterator in = getInputs().iterator(); - Iterator out = getOutputs().iterator(); - panel.visitTransformAllChildren(widget -> transformWidget(widget, in, out)); + panel.visitTransformAllChildren(this::transformWidget); return panel; } - public IWidget transformWidget(IWidget widget, Iterator in, Iterator out) { + public IWidget transformWidget(IWidget widget) { if (!(widget instanceof EmiRecipeViewerSlot recipeViewerSlot)) return widget; if (recipeViewerSlot.recipeSlotRole() == RecipeSlotRole.OUTPUT) { @@ -158,44 +156,60 @@ public IWidget transformWidget(IWidget widget, Iterator in, Itera return recipeViewerSlot; } + private boolean useScreenCacheForNextWidgetQuery = false; + + /** + * This is used to make EMI's recipe tooltip components use the screen cache because those normally recreate the widgets every frame + */ + @ApiStatus.Internal + public final void useScreenCacheForNextWidgetQuery() { + useScreenCacheForNextWidgetQuery = true; + } + @Override public void addWidgets(WidgetHolder widgets) { - // emi complains when it cant find an output slot - widgets.add(new SlotWidget(EmiStack.EMPTY, -1000, -1000).drawBack(false).recipeContext(this)); - widgets.add(new UIWrapperWidget(this)); + if (this.supportsRecipeTree()) { + // emi complains when it cant find an output slot + widgets.add(new SlotWidget(EmiStack.EMPTY, -1000, -1000).drawBack(false).recipeContext(this)); + } + widgets.add(new UIWrapperWidget(this, useScreenCacheForNextWidgetQuery)); + useScreenCacheForNextWidgetQuery = false; } public static class UIWrapperWidget extends Widget { - private final ModularUIEmiRecipe recipe; + private final ModularScreen screen; - public UIWrapperWidget(ModularUIEmiRecipe recipe) { - this.recipe = recipe; - } + @Getter + private final Bounds bounds; - @Override - public Bounds getBounds() { - return this.recipe.getBounds(); + public UIWrapperWidget(ModularUIEmiRecipe recipe, boolean useScreenCache) { + this.screen = useScreenCache ? getCachedModularScreen(recipe) : recipe.createScreen(); + + if (recipe.sizeCalculated) { + this.bounds = recipe.getBounds(); + } else { + this.bounds = new Bounds(0, 0, EmbedHandler.getEmbedWidth(screen), EmbedHandler.getEmbedHeight(screen)); + } } @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - ModularScreen screen = getModularScreen(this.recipe); - EmbedHandler.drawEmbed(screen, graphics, mouseX, mouseY, partialTick, r -> !(r instanceof SizedButtonWidget)); + EmbedHandler.drawEmbed(this.screen, graphics, mouseX, mouseY, partialTick, r -> !(r instanceof SizedButtonWidget)); } @Override public List getTooltip(int mouseX, int mouseY) { - ModularScreen screen = getModularScreen(this.recipe); - if (!screen.getContext().getUISettings().drawTooltipExternally()) { + if (!this.screen.getContext().getUISettings().drawTooltipExternally()) { return super.getTooltip(mouseX, mouseY); } - IWidget hovered = screen.getContext().getTopHovered(); + IWidget hovered = this.screen.getContext().getTopHovered(); if (hovered instanceof ITooltip tooltip && tooltip.getTooltip() != null) { RichTooltip richTooltip = tooltip.getTooltip(); if (richTooltip.autoUpdate()) richTooltip.markDirty(); - richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary + // causes the tooltip to rebuild if necessary + if (richTooltip.isEmpty()) return List.of(); if (richTooltip.getRichText() instanceof RichText richText) { // scuffed conversion, but it mostly works @@ -207,34 +221,24 @@ public List getTooltip(int mouseX, int mouseY) { @Override public boolean mouseClicked(int mouseX, int mouseY, int button) { - return getModularScreen(this.recipe).mousePressed(button); + return this.screen.mousePressed(button); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - return getModularScreen(this.recipe).keyPressed(keyCode, scanCode, modifiers); + return this.screen.keyPressed(keyCode, scanCode, modifiers); } public boolean mouseScrolled(double mouseX, double mouseY, double delta) { - return getModularScreen(this.recipe).mouseScrolled(delta); + return this.screen.mouseScrolled(delta); } public boolean mouseDragged(int button, double dragX, double dragY) { - return getModularScreen(this.recipe).mouseDragged(button, dragX, dragY); + return this.screen.mouseDragged(button, dragX, dragY); } public boolean mouseReleased(int button) { - return getModularScreen(this.recipe).mouseReleased(button); - } - } - - private static final StackWalker STAR_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); - - @ApiStatus.Internal - public static void clearScreenCache() { - if (!STAR_WALKER.getCallerClass().equals(ModularUIEmiRecipe.class)) { - throw new IllegalCallerException("Attempted to call ModularUIEmiRecipe#clearScreenCache!"); + return this.screen.mouseReleased(button); } - SCREEN_CACHE.invalidateAll(); } } diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java index f7faeb7e..22756bfa 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java @@ -1,5 +1,6 @@ package brachy.modularui.integration.jei.recipe; +import brachy.modularui.ModularUI; import brachy.modularui.api.drawable.IRichTextBuilder; import brachy.modularui.api.widget.ITooltip; import brachy.modularui.api.widget.IWidget; @@ -17,6 +18,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.navigation.ScreenPosition; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -24,8 +26,6 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; import mezz.jei.api.gui.builder.IRecipeSlotBuilder; import mezz.jei.api.gui.builder.ITooltipBuilder; @@ -34,6 +34,7 @@ import mezz.jei.api.gui.ingredient.IRecipeSlotsView; import mezz.jei.api.gui.inputs.IJeiGuiEventListener; import mezz.jei.api.gui.widgets.IRecipeExtrasBuilder; +import mezz.jei.api.gui.widgets.IRecipeWidget; import mezz.jei.api.recipe.IFocusGroup; import mezz.jei.api.recipe.category.IRecipeCategory; import org.apache.commons.lang3.mutable.MutableInt; @@ -42,6 +43,8 @@ import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @ApiStatus.Experimental @@ -49,9 +52,6 @@ public abstract class ModularUIJeiCategory implements IRecipeCategory { public static final String SCREEN_NAME_PREFIX = "jei_recipe_"; - // These don't need special clearing because they're instance fields so the category instance being recreated by JEI is enough to - // free the class instance for GC (and thus free & clear these) - private final LoadingCache modularScreenCache; private final LoadingCache displaySizeCache; private final Function recipeUI; @@ -61,25 +61,6 @@ protected ModularUIJeiCategory(Function recipeUI, Function() { - @Override - public ModularScreen load(T recipe) { - try { - return ModularUIJeiCategory.this.createScreen(recipe); - } finally { - ModularUIJeiCategory.this.displaySizeCache.refresh(recipe); - } - } - - @Override - public ListenableFuture reload(T key, ModularScreen oldValue) { - // if an old value is (somehow) available, reuse it - return Futures.immediateFuture(oldValue); - } - }); this.displaySizeCache = CacheBuilder.newBuilder() .initialCapacity(64) .build(new CacheLoader<>() { @@ -90,10 +71,6 @@ public Dimensions load(T recipe) { }); } - private ModularScreen getModularScreen(T recipe) { - return this.modularScreenCache.getUnchecked(recipe); - } - /** * Calculates the size of the recipe. */ @@ -219,14 +196,18 @@ public Component getTitle() { return RecipeViewerUtils.getCategoryTitle(this.getRecipeType().getUid()); } + // this is a map instead of a simple field so mods that make JEI loading asynchronous work as expected + private final Map veryTemporaryScreenCache = new ConcurrentHashMap<>(); + @Override public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { // guard against JEMI issues by explicitly checking for JEI's implementation // this is also done to skip having to create the whole widget tree when JEI is only looking up the recipe's ingredients if (builder instanceof RecipeLayoutBuilderAccessor) { - ModularScreen screen = getModularScreen(recipe); + ModularScreen screen = createScreen(recipe); MutableInt index = new MutableInt(0); screen.getMainPanel().visitTransformAllChildren(widget -> createRecipeSlotForWidget(builder, widget, recipe, focuses, index)); + veryTemporaryScreenCache.put(recipe, screen); } else { // don't bother with creating the full widget tree if setRecipe was called to get the recipe's ingredients this.setupRecipeIngredients(builder, recipe, focuses); @@ -235,41 +216,21 @@ public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuse @Override public void createRecipeExtras(IRecipeExtrasBuilder builder, T recipe, IFocusGroup focuses) { - ModularScreen screen = getModularScreen(recipe); - - screen.getMainPanel().visitTransformAllChildren(widget -> transformWidget(builder, widget)); - builder.addGuiEventListener(new ModularUIGuiEventListener(screen)); - } - - @Override - public void getTooltip(ITooltipBuilder tooltipBuilder, T recipe, IRecipeSlotsView recipeSlotsView, double mouseX, double mouseY) { - ModularScreen screen = getModularScreen(recipe); - if (!screen.getContext().getUISettings().drawTooltipExternally()) { - IRecipeCategory.super.getTooltip(tooltipBuilder, recipe, recipeSlotsView, mouseX, mouseY); + ModularScreen screen = veryTemporaryScreenCache.get(recipe); + if (screen == null) { + ModularUI.LOGGER.error("Could not get cached screen for recipe {} somehow?!", recipe); return; } + veryTemporaryScreenCache.remove(recipe); - IWidget hovered = screen.getContext().getTopHovered(); - if (hovered instanceof ITooltip tooltip && tooltip.getTooltip() != null) { - RichTooltip richTooltip = tooltip.getTooltip(); - if (richTooltip.autoUpdate()) richTooltip.markDirty(); - richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary - - IRichTextBuilder richTextBuilder = richTooltip.getRichText(); - if (richTextBuilder instanceof RichText richText) { - for (var line : richText.getAsText()) { - // scuffed conversion, but it mostly works - line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); - } - } - } + screen.getMainPanel().visitTransformAllChildren(widget -> transformWidget(builder, widget)); + UIWrapperWidget wrapper = new UIWrapperWidget(screen); + builder.addGuiEventListener(wrapper); + builder.addWidget(wrapper); } @Override - public void draw(T recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) { - ModularScreen screen = getModularScreen(recipe); - EmbedHandler.drawEmbed(screen, graphics, (int) mouseX, (int) mouseY, Minecraft.getInstance().getPartialTick()); - } + public void getTooltip(ITooltipBuilder tooltipBuilder, T recipe, IRecipeSlotsView recipeSlotsView, double mouseX, double mouseY) {} @Override public @Nullable IDrawable getIcon() { @@ -298,11 +259,11 @@ public int getWidth(T recipe) { protected record Dimensions(int width, int height) { } - public static class ModularUIGuiEventListener implements IJeiGuiEventListener { + public static class UIWrapperWidget implements IJeiGuiEventListener, IRecipeWidget { private final ModularScreen screen; - public ModularUIGuiEventListener(ModularScreen screen) { + public UIWrapperWidget(ModularScreen screen) { this.screen = screen; } @@ -310,6 +271,39 @@ public ScreenRectangle getArea() { return this.screen.getMainRectangle(); } + @Override + public ScreenPosition getPosition() { + return getArea().position(); + } + + @Override + public void drawWidget(GuiGraphics graphics, double mouseX, double mouseY) { + EmbedHandler.drawEmbed(this.screen, graphics, (int) mouseX, (int) mouseY, Minecraft.getInstance().getPartialTick()); + } + + @Override + public void getTooltip(ITooltipBuilder tooltipBuilder, double mouseX, double mouseY) { + if (!this.screen.getContext().getUISettings().drawTooltipExternally()) { + IRecipeWidget.super.getTooltip(tooltipBuilder, mouseX, mouseY); + return; + } + + IWidget hovered = screen.getContext().getTopHovered(); + if (hovered instanceof ITooltip tooltip && tooltip.getTooltip() != null) { + RichTooltip richTooltip = tooltip.getTooltip(); + if (richTooltip.autoUpdate()) richTooltip.markDirty(); + richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary + + IRichTextBuilder richTextBuilder = richTooltip.getRichText(); + if (richTextBuilder instanceof RichText richText) { + for (var line : richText.getAsText()) { + // scuffed conversion, but it mostly works + line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); + } + } + } + } + @Override public void mouseMoved(double mouseX, double mouseY) { //this.screen.mouseMoved(mouseX, mouseY); diff --git a/src/main/java/brachy/modularui/integration/rei/ModularUIReiPlugin.java b/src/main/java/brachy/modularui/integration/rei/ModularUIReiPlugin.java index 68c64c65..5d6a1bf7 100644 --- a/src/main/java/brachy/modularui/integration/rei/ModularUIReiPlugin.java +++ b/src/main/java/brachy/modularui/integration/rei/ModularUIReiPlugin.java @@ -2,13 +2,11 @@ import brachy.modularui.ModularUI; import brachy.modularui.integration.rei.handler.REIScreenHandler; -import brachy.modularui.integration.rei.recipe.ModularUIReiDisplay; import brachy.modularui.screen.ContainerScreenWrapper; import brachy.modularui.screen.ScreenWrapper; import brachy.modularui.test.TestRecipeViewerGuis; -import me.shedaniel.rei.api.client.entry.renderer.EntryRendererRegistry; import me.shedaniel.rei.api.client.plugins.REIClientPlugin; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; @@ -19,13 +17,6 @@ @REIPluginClient public class ModularUIReiPlugin implements REIClientPlugin { - /// This is only used to clear the stale recipe display "screen" cache. - @SuppressWarnings("UnstableApiUsage") - @Override - public void registerEntryRenderers(EntryRendererRegistry registry) { - ModularUIReiDisplay.clearScreenCache(); - } - @Override public void registerExclusionZones(ExclusionZones zones) { zones.register(ScreenWrapper.class, REIScreenHandler.of(ScreenWrapper.class)); diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java index 03e6f6d0..4b4cc540 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java @@ -6,7 +6,6 @@ import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.util.RecipeDebugDecoratorUtil; -import brachy.modularui.integration.rei.ModularUIReiPlugin; import brachy.modularui.integration.rei.ReiRecipeViewerSlot; import brachy.modularui.screen.EmbedHandler; import brachy.modularui.screen.ModularPanel; @@ -45,25 +44,6 @@ public abstract class ModularUIReiDisplay implements Display { private static final String SCREEN_NAME_PREFIX = "rei_display_"; - private static final LoadingCache SCREEN_CACHE = CacheBuilder.newBuilder() - .initialCapacity(64) - .softValues() - .build(new CacheLoader<>() { - @Override - public ModularScreen load(ModularUIReiDisplay key) { - return key.createScreen(); - } - - @Override - public ListenableFuture reload(ModularUIReiDisplay key, ModularScreen oldValue) { - // if an old value is (somehow) available, reuse it - return Futures.immediateFuture(oldValue); - } - }); - - private static ModularScreen getModularScreen(ModularUIReiDisplay display) { - return SCREEN_CACHE.getUnchecked(display); - } private final ResourceLocation recipeId; private final Supplier recipeUI; @@ -164,20 +144,18 @@ public List createWidgets(Rectangle bounds) { public static class UIWrapperWidget extends Widget { - private final ModularUIREIDisplay display; + private final ModularScreen screen; private final float offsetX, offsetY; public UIWrapperWidget(ModularUIReiDisplay display, float offsetX, float offsetY) { - this.display = display; + this.screen = display.createScreen(); this.offsetX = offsetX; this.offsetY = offsetY; } @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - ModularScreen screen = getModularScreen(this.display); - // FIXME this thing breaks if you have the same recipe open in REI and pinned as a favorite. - EmbedHandler.drawEmbed(screen, graphics, mouseX, mouseY, partialTick, () -> { + EmbedHandler.drawEmbed(this.screen, graphics, mouseX, mouseY, partialTick, () -> { screen.getContext().pushMatrix(); screen.getContext().translate(this.offsetX, this.offsetY); }, () -> { @@ -187,12 +165,11 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTi @Override public @Nullable Tooltip getTooltip(TooltipContext context) { - ModularScreen screen = getModularScreen(this.display); - if (!screen.getContext().getUISettings().drawTooltipExternally()) { + if (!this.screen.getContext().getUISettings().drawTooltipExternally()) { return super.getTooltip(context); } - IWidget hovered = screen.getContext().getTopHovered(); + IWidget hovered = this.screen.getContext().getTopHovered(); if (hovered instanceof ITooltip tooltip && tooltip.getTooltip() != null) { RichTooltip richTooltip = tooltip.getTooltip(); if (richTooltip.autoUpdate()) richTooltip.markDirty(); @@ -218,52 +195,42 @@ public List children() { @Override public void mouseMoved(double mouseX, double mouseY) { - //getModularScreen(this.display).mouseMoved(mouseX, mouseY); + //this.screen.mouseMoved(mouseX, mouseY); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - return getModularScreen(this.display).mousePressed(button); + return this.screen.mousePressed(button); } @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { - return getModularScreen(this.display).mouseReleased(button); + return this.screen.mouseReleased(button); } @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { - return getModularScreen(this.display).mouseDragged(button, dragX, dragY); + return this.screen.mouseDragged(button, dragX, dragY); } @Override public boolean mouseScrolled(double mouseX, double mouseY, double scrollDelta) { - return getModularScreen(this.display).mouseScrolled(scrollDelta); + return this.screen.mouseScrolled(scrollDelta); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - return getModularScreen(this.display).keyPressed(keyCode, scanCode, modifiers); + return this.screen.keyPressed(keyCode, scanCode, modifiers); } @Override public boolean keyReleased(int keyCode, int scanCode, int modifiers) { - return getModularScreen(this.display).keyReleased(keyCode, scanCode, modifiers); + return this.screen.keyReleased(keyCode, scanCode, modifiers); } @Override public boolean charTyped(char codePoint, int modifiers) { - return getModularScreen(this.display).charTyped(codePoint, modifiers); - } - } - - private static final StackWalker BETTER_CALL_SAUL = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); - - @ApiStatus.Internal - public static void clearScreenCache() { - if (!BETTER_CALL_SAUL.getCallerClass().equals(ModularUIReiPlugin.class)) { - throw new IllegalCallerException("Attempted to call ModularUIREIDisplay#clearScreenCache!"); + return this.screen.charTyped(codePoint, modifiers); } - SCREEN_CACHE.invalidateAll(); } } diff --git a/src/main/resources/modularui.mixins.json b/src/main/resources/modularui.mixins.json index 1e805415..2ba99e4b 100755 --- a/src/main/resources/modularui.mixins.json +++ b/src/main/resources/modularui.mixins.json @@ -11,6 +11,7 @@ "client.MinecraftMixin", "client.ScreenAccessor", "client.StringSplitterAccessor", + "emi.EmiRenderHelperMixin", "emi.RecipeScreenMixin", "jei.IngredientListOverlayAccessor", "rei.DefaultDisplayViewingScreenMixin" From e5210d3effa9c053faf57540cd8c9e7f4dcf9543 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:36:33 +0300 Subject: [PATCH 60/84] Minor tweaks to JEI category & slot code --- .../integration/jei/JeiRecipeViewerSlot.java | 52 +++++++++++++------ .../jei/ModularUIJeiProperties.java | 8 +-- .../jei/recipe/ModularUIJeiCategory.java | 13 +++-- .../modularui/test/TestRecipeViewerGuis.java | 8 --- 4 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index eaa58d14..2cf27cd4 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -12,6 +12,7 @@ import brachy.modularui.theme.WidgetThemeEntry; +import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntSet; import lombok.Getter; import lombok.Setter; @@ -21,6 +22,7 @@ import mezz.jei.api.gui.widgets.ISlottedRecipeWidget; import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.recipe.RecipeType; import mezz.jei.api.recipe.category.IRecipeCategory; import mezz.jei.api.runtime.IIngredientManager; @@ -33,18 +35,20 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static brachy.modularui.integration.jei.ModularUIJeiPlugin.mapToJeiRole; @ApiStatus.Internal -public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget> implements ISlottedRecipeWidget { +public class JeiRecipeViewerSlot extends RecipeViewerSlotWidget> implements ISlottedRecipeWidget { @Getter private @Nullable IRecipeSlotDrawable slotWidget; @Setter private IFocusGroup focuses; @Setter private ICycler cycler; - @Setter private IRecipeCategory recipeCategory; - @Setter private R recipe; + @Setter private IRecipeCategory recipeCategory; + @Setter private T recipe; @Setter private IIngredientManager ingredientManager; @@ -71,17 +75,21 @@ protected void rebuildRealSlot() { return; } - // always remove the output slot tooltip callback (this is easier than checking if it already exists etc.) - recipeSlot.modularui$getTooltipCallbacks().removeIf(callback -> callback instanceof OutputSlotTooltipCallback); - if (this.recipeSlotRole == RecipeSlotRole.OUTPUT) { - // (re)add the output slot tooltip callback if this is now an output slot - addOutputSlotTooltipCallback(recipeSlot); + RecipeIngredientRole jeiIngredientRole = mapToJeiRole(this.recipeSlotRole); + if (slotWidget.getRole() != jeiIngredientRole) { + // always remove the output slot tooltip callback (this is easier than checking if it already exists etc.) + recipeSlot.modularui$getTooltipCallbacks().removeIf(callback -> callback instanceof OutputSlotTooltipCallback); + if (this.recipeSlotRole == RecipeSlotRole.OUTPUT) { + // (re)add the output slot tooltip callback if this is now an output slot + addOutputSlotTooltipCallback(recipeSlot); + } + recipeSlot.modularui$setRole(jeiIngredientRole); } - recipeSlot.modularui$setRole(mapToJeiRole(this.recipeSlotRole)); // mmm I love off-by-one errors slotWidget.setPosition(1, 1); - replaceSlotIngredients(recipeSlot); + replaceSlotIngredients(recipeSlot, jeiIngredientRole); + recipeSlot.modularui$setCycler(this.cycler); } @@ -97,7 +105,7 @@ public void configureJeiSlotBuilder(IRecipeSlotBuilder builder) { } // Mostly copied from RecipeSlotBuilder#build - private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { + private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot, RecipeIngredientRole ingredientRole) { if (this.ingredientManager == null) { if (ModularUIJeiPlugin.jeiHelpers == null) { return; @@ -112,12 +120,26 @@ private void replaceSlotIngredients(RecipeSlotAccessor recipeSlot) { List>> allIngredients = ingredients.getAllIngredients(); - IntSet focusMatches = ingredients.getMatches(this.focuses, mapToJeiRole(this.recipeSlotRole)); - List>> focusedIngredients = null; + // recipeSlot is the same object as this.slotWidget + // noinspection DataFlowIssue + if (!slotWidget.isEmpty()) { + // check if the slot's ingredients are the same as our new ones and skip replacing them if so + // this lets us optimize lookup by not parsing the focused ingredients if possible + Set> newIngredients = allIngredients.stream().flatMap(Optional::stream).collect(Collectors.toSet()); + slotWidget.getAllIngredients().forEach(newIngredients::remove); + + // every ingredient was removed -> the ingredient lists are equal + if (newIngredients.isEmpty()) { + return; + } + } + IntSet focusMatches = ingredients.getMatches(this.focuses, ingredientRole); + List>> focusedIngredients = null; if (!focusMatches.isEmpty()) { focusedIngredients = new ArrayList<>(); - for (Integer i : focusMatches) { + for (IntIterator iterator = focusMatches.iterator(); iterator.hasNext(); ) { + int i = iterator.nextInt(); if (i < allIngredients.size()) { Optional> ingredient = allIngredients.get(i); focusedIngredients.add(ingredient); @@ -168,7 +190,7 @@ public void drawWidget(GuiGraphics guiGraphics, double mouseX, double mouseY) {} private void addOutputSlotTooltipCallback(RecipeSlotAccessor slot) { ResourceLocation recipeName = recipeCategory.getRegistryName(recipe); if (recipeName != null) { - RecipeType recipeType = recipeCategory.getRecipeType(); + RecipeType recipeType = recipeCategory.getRecipeType(); OutputSlotTooltipCallback callback = new OutputSlotTooltipCallback(recipeName, recipeType); slot.modularui$getTooltipCallbacks().add(callback); } diff --git a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiProperties.java b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiProperties.java index fad258ff..423680f0 100644 --- a/src/main/java/brachy/modularui/integration/jei/ModularUIJeiProperties.java +++ b/src/main/java/brachy/modularui/integration/jei/ModularUIJeiProperties.java @@ -49,6 +49,7 @@ public ModularUIJeiProperties(IMuiScreen screen) { Area mainArea = screen.screen().getMainPanel().getArea(); this.guiXSize = mainArea.width == 0 ? DEFAULT_GUI_WIDTH : mainArea.width; this.guiYSize = mainArea.height == 0 ? DEFAULT_GUI_HEIGHT : mainArea.height; + // don't check mainArea x/y here because those can actually be 0 this.guiLeft = mainArea.width == 0 ? (this.screenWidth - DEFAULT_GUI_WIDTH) / 2 : mainArea.x; this.guiTop = mainArea.height == 0 ? (this.screenHeight - DEFAULT_GUI_HEIGHT) / 2 : mainArea.y; } @@ -70,9 +71,10 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ModularUIJeiProperties that = (ModularUIJeiProperties) o; - return guiLeft == that.guiLeft && guiTop == that.guiTop && guiXSize == that.guiXSize && guiYSize == that.guiYSize && - screenWidth == that.screenWidth && screenHeight == that.screenHeight && - Objects.equals(screenClass, that.screenClass); + return this.guiLeft == that.guiLeft && this.guiTop == that.guiTop && + this.guiXSize == that.guiXSize && this.guiYSize == that.guiYSize && + this.screenWidth == that.screenWidth && this.screenHeight == that.screenHeight && + Objects.equals(this.screenClass, that.screenClass); } @Override diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java index 22756bfa..099f44b3 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java @@ -80,7 +80,7 @@ protected Dimensions calculateSize(T recipe) { ResourceLocation id = this.recipeIdGetter.apply(recipe); int w = ui.resizer().getFixedPixelWidth(), h = ui.resizer().getFixedPixelHeight(); if (w < 0 || h < 0) { - ModularScreen screen = createScreen(ui, id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath()); + ModularScreen screen = createScreen(ui, id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath(), recipe); w = EmbedHandler.getEmbedWidth(screen); h = EmbedHandler.getEmbedHeight(screen); } @@ -122,17 +122,15 @@ protected final void calculateAndCacheSize(T recipe) { * Note that you can only add inputs and outputs for JEI's recipe lookup/search here, as the layout builder that's * passed into this method only handles those and not the displayed recipe previews. */ - @MustBeInvokedByOverriders - public void setupRecipeIngredients(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { - calculateAndCacheSize(recipe); - } + @ApiStatus.OverrideOnly + public abstract void setupRecipeIngredients(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses); private ModularScreen createScreen(T recipe) { ResourceLocation id = this.recipeIdGetter.apply(recipe); - return createScreen(this.recipeUI.apply(recipe), id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath()); + return createScreen(this.recipeUI.apply(recipe), id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath(), recipe); } - public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { + public ModularScreen createScreen(IWidget recipeUI, String owner, String name, T recipe) { ModularPanel panel; if (recipeUI instanceof ModularPanel panel1) { panel = panel1; @@ -199,6 +197,7 @@ public Component getTitle() { // this is a map instead of a simple field so mods that make JEI loading asynchronous work as expected private final Map veryTemporaryScreenCache = new ConcurrentHashMap<>(); + @MustBeInvokedByOverriders @Override public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses) { // guard against JEMI issues by explicitly checking for JEI's implementation diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index 8d3e25c6..cbe1ec90 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -1,12 +1,8 @@ package brachy.modularui.test; -import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.fluids.IFluidTank; -import net.minecraftforge.items.IItemHandler; -import net.minecraftforge.items.wrapper.EmptyHandler; import brachy.modularui.ModularUI; import brachy.modularui.api.widget.IWidget; @@ -18,9 +14,6 @@ import brachy.modularui.integration.rei.recipe.ModularUIReiDisplay; import brachy.modularui.integration.rei.recipe.ModularUIReiCategory; import brachy.modularui.screen.ModularPanel; -import brachy.modularui.utils.handlers.fluid.EmptyFluidTank; -import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; -import brachy.modularui.utils.handlers.fluid.MultiTankFluidHandler; import brachy.modularui.value.DoubleValue; import brachy.modularui.widgets.slot.FluidSlot; import brachy.modularui.widgets.slot.ItemSlot; @@ -174,7 +167,6 @@ public int getMaxHeight() { @Override public void setupRecipeIngredients(IRecipeLayoutBuilder builder, TestMachine.Recipe recipe, IFocusGroup focuses) { - super.setupRecipeIngredients(builder, recipe, focuses); recipe.inItems.forEach(item -> builder.addSlot(RecipeIngredientRole.INPUT).addItemStack(item)); recipe.outItems.forEach(item -> builder.addSlot(RecipeIngredientRole.OUTPUT).addItemStack(item)); } From 330d699e570a15debafb27b00ee8d4d6e5a84af7 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:37:14 +0300 Subject: [PATCH 61/84] add more API annotations --- .../modularui/integration/emi/recipe/ModularUIEmiRecipe.java | 5 +++++ .../integration/jei/recipe/ModularUIJeiCategory.java | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 4cc63a7a..8c5cedfa 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -29,6 +29,7 @@ import dev.emi.emi.screen.widget.SizedButtonWidget; import lombok.Getter; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.MustBeInvokedByOverriders; import java.time.Duration; import java.util.List; @@ -122,6 +123,7 @@ private ModularScreen createScreen() { return createScreen(this.recipeUI.get(), this.id.getNamespace(), SCREEN_NAME_PREFIX + this.id.getPath()); } + @ApiStatus.OverrideOnly public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { ModularPanel panel; if (recipeUI instanceof ModularPanel panel1) { @@ -141,11 +143,13 @@ public ModularScreen createScreen(IWidget recipeUI, String owner, String name) { return screen; } + @ApiStatus.OverrideOnly public ModularPanel transform(ModularPanel panel) { panel.visitTransformAllChildren(this::transformWidget); return panel; } + @ApiStatus.OverrideOnly public IWidget transformWidget(IWidget widget) { if (!(widget instanceof EmiRecipeViewerSlot recipeViewerSlot)) return widget; @@ -166,6 +170,7 @@ public final void useScreenCacheForNextWidgetQuery() { useScreenCacheForNextWidgetQuery = true; } + @MustBeInvokedByOverriders @Override public void addWidgets(WidgetHolder widgets) { if (this.supportsRecipeTree()) { diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java index 099f44b3..44616f00 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java @@ -130,6 +130,7 @@ private ModularScreen createScreen(T recipe) { return createScreen(this.recipeUI.apply(recipe), id.getNamespace(), SCREEN_NAME_PREFIX + id.getPath(), recipe); } + @ApiStatus.OverrideOnly public ModularScreen createScreen(IWidget recipeUI, String owner, String name, T recipe) { ModularPanel panel; if (recipeUI instanceof ModularPanel panel1) { @@ -168,6 +169,7 @@ public IWidget transformWidget(IRecipeExtrasBuilder builder, IWidget widget) { @SuppressWarnings({ "rawtypes", "unchecked" }) @ApiStatus.OverrideOnly + @MustBeInvokedByOverriders public IWidget createRecipeSlotForWidget(IRecipeLayoutBuilder builder, IWidget widget, T recipe, IFocusGroup focuses, MutableInt index) { if (!(widget instanceof JeiRecipeViewerSlot recipeViewerSlot)) return widget; @@ -213,6 +215,7 @@ public void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuse } } + @MustBeInvokedByOverriders @Override public void createRecipeExtras(IRecipeExtrasBuilder builder, T recipe, IFocusGroup focuses) { ModularScreen screen = veryTemporaryScreenCache.get(recipe); From 207bc5db2c57ef9cba19083b2109ba3b0ea0a659 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:37:58 +0300 Subject: [PATCH 62/84] Try to fix recipe viewer debug decorator --- .../RecipeViewerScreenWrapper.java | 26 -------------- .../util/RecipeDebugDecoratorUtil.java | 34 +++++++++++-------- .../brachy/modularui/screen/ModularPanel.java | 24 +++++++++++-- 3 files changed, 40 insertions(+), 44 deletions(-) delete mode 100644 src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerScreenWrapper.java diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerScreenWrapper.java b/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerScreenWrapper.java deleted file mode 100644 index ff63a13e..00000000 --- a/src/main/java/brachy/modularui/integration/recipeviewer/RecipeViewerScreenWrapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package brachy.modularui.integration.recipeviewer; - -import brachy.modularui.api.IMuiScreen; -import brachy.modularui.screen.ModularScreen; -import brachy.modularui.utils.Rectangle; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; - -import lombok.experimental.Accessors; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Experimental -@Accessors(fluent = true) -public record RecipeViewerScreenWrapper(ModularScreen screen) implements IMuiScreen { - - @Override - public Screen wrappedScreen() { - return Minecraft.getInstance().screen; - } - - @Override - public void updateGuiArea(Rectangle area) { - // overlay should not modify screen - } -} diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java index 60ee7a38..68808357 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java @@ -1,8 +1,7 @@ package brachy.modularui.integration.recipeviewer.util; -import net.minecraft.client.Minecraft; - import brachy.modularui.ModularUIConfig; +import brachy.modularui.api.IMuiScreen; import brachy.modularui.overlay.DebugOverlay; import brachy.modularui.overlay.OverlayStack; import brachy.modularui.screen.ModularScreen; @@ -21,22 +20,27 @@ public static void addRecipeDebugOverlays(ModularScreen screen) { } MutableObject debugOverlay = new MutableObject<>(); - Runnable close = () -> { - ModularScreen overlay = debugOverlay.getValue(); - if (overlay != null) { - OverlayStack.close(overlay); - } - }; screen.getMainPanel() - .onMouseEnterArea((ctx) -> { - ModularScreen overlay = new DebugOverlay(screen.getScreenWrapper()); - overlay.constructOverlay(screen.getScreenWrapper().wrappedScreen()); - OverlayStack.open(overlay); - + .onOpenAction(panel -> { + if (debugOverlay.getValue() != null) { + return; + } + IMuiScreen screenWrapper = panel.getScreen().getScreenWrapper(); + + ModularScreen overlay = new DebugOverlay(screenWrapper); + // set this ASAP to avoid the possibility of double init debugOverlay.setValue(overlay); + overlay.constructOverlay(screenWrapper.wrappedScreen()); + OverlayStack.open(overlay); }) - .onMouseLeaveArea((ctx, timeBelowMouse) -> close.run()) - .onCloseAction(close); + .onCloseAction(panel -> { + ModularScreen overlay = debugOverlay.getValue(); + if (overlay == null) { + return; + } + OverlayStack.close(overlay); + debugOverlay.setValue(null); + }); } } diff --git a/src/main/java/brachy/modularui/screen/ModularPanel.java b/src/main/java/brachy/modularui/screen/ModularPanel.java index 71cb055d..c8141d31 100644 --- a/src/main/java/brachy/modularui/screen/ModularPanel.java +++ b/src/main/java/brachy/modularui/screen/ModularPanel.java @@ -43,6 +43,7 @@ import java.util.List; import java.util.Objects; import java.util.function.BooleanSupplier; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.IntSupplier; import java.util.function.Supplier; @@ -92,7 +93,8 @@ public static ModularPanel defaultPanel(@NotNull String name, int width, int private String themeOverride; private ITheme theme; - private Runnable onCloseAction; + private Consumer onOpenAction; + private Consumer onCloseAction; private boolean resizeable = false; /** * True if this panel can be dragged. Never works on the main panel. @@ -257,17 +259,26 @@ public void onOpen(ModularScreen screen) { getAnimator().animate(); } this.state = State.OPEN; + + if (this.onOpenAction != null) { + this.onOpenAction.accept(getThis()); + } } void reopen() { this.state = State.OPEN; + + if (this.onOpenAction != null) { + this.onOpenAction.accept(getThis()); + } } @MustBeInvokedByOverriders public void onClose() { if (this.onCloseAction != null) { - this.onCloseAction.run(); + this.onCloseAction.accept(getThis()); } + this.state = State.CLOSED; if (this.panelHandler != null) { this.panelHandler.closePanelInternal(); @@ -849,7 +860,14 @@ public W closeOnOutOfBoundsClick(boolean closeOnOutOfBoundsClick) { return getThis(); } - public W onCloseAction(Runnable onCloseAction) { + public W onOpenAction(Consumer onOpenAction) { + this.onOpenAction = onOpenAction; + if (this.isOpen()) onOpenAction.accept(getThis()); + + return getThis(); + } + + public W onCloseAction(Consumer onCloseAction) { this.onCloseAction = onCloseAction; return getThis(); } From d92201a249d9e6f8ab40522f2363d716172954f0 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:38:28 +0300 Subject: [PATCH 63/84] Fix capitalization of `GuiContext.uiType` --- .../java/brachy/modularui/screen/ModularPanel.java | 2 +- .../java/brachy/modularui/screen/ModularScreen.java | 4 ++-- .../java/brachy/modularui/screen/RichTooltip.java | 2 +- .../brachy/modularui/screen/viewport/GuiContext.java | 11 +++++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/brachy/modularui/screen/ModularPanel.java b/src/main/java/brachy/modularui/screen/ModularPanel.java index c8141d31..15efa5f1 100644 --- a/src/main/java/brachy/modularui/screen/ModularPanel.java +++ b/src/main/java/brachy/modularui/screen/ModularPanel.java @@ -154,7 +154,7 @@ public boolean isOpen() { * If animating is enabled and an animation is already playing this method will do nothing. */ public void closeIfOpen() { - if (!isOpen() || (!getContext().getUItype().isScreen && isMainPanel())) return; + if (!isOpen() || (!getContext().getUIType().isScreen && isMainPanel())) return; closeSubPanels(); if (isMainPanel()) { // close screen and let NEA handle animation // TODO: since nea is not yet ported, it will just close the diff --git a/src/main/java/brachy/modularui/screen/ModularScreen.java b/src/main/java/brachy/modularui/screen/ModularScreen.java index 8e3bc7c2..96724369 100644 --- a/src/main/java/brachy/modularui/screen/ModularScreen.java +++ b/src/main/java/brachy/modularui/screen/ModularScreen.java @@ -349,7 +349,7 @@ private void checkManualUpdate() { */ @Override public void render(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - if (!this.context.getUItype().isScreen) { + if (!this.context.getUIType().isScreen) { checkManualUpdate(); // embeds can't trigger frame updates the proper way } this.context.setGraphics(graphics); @@ -664,7 +664,7 @@ public Area getScreenArea() { } public boolean isClientOnly() { - return isOverlay() || getContext().getUItype() != UIType.MODULAR_SCREEN + return isOverlay() || getContext().getUIType() != UIType.MODULAR_SCREEN || !this.screenWrapper.isContainerScreen() || getContainer().isClientOnly(); } diff --git a/src/main/java/brachy/modularui/screen/RichTooltip.java b/src/main/java/brachy/modularui/screen/RichTooltip.java index 72522f1c..a03611e7 100644 --- a/src/main/java/brachy/modularui/screen/RichTooltip.java +++ b/src/main/java/brachy/modularui/screen/RichTooltip.java @@ -378,7 +378,7 @@ public RichTooltip getThis() { @Override public IRichTextBuilder getRichText() { - return text; + return this.text; } public RichTooltip tooltipBuilder(Consumer tooltipBuilder) { diff --git a/src/main/java/brachy/modularui/screen/viewport/GuiContext.java b/src/main/java/brachy/modularui/screen/viewport/GuiContext.java index a83802c3..dbfe84c9 100644 --- a/src/main/java/brachy/modularui/screen/viewport/GuiContext.java +++ b/src/main/java/brachy/modularui/screen/viewport/GuiContext.java @@ -35,8 +35,7 @@ public static GuiContext getDefault() { return ClientScreenHandler.getBestContext(); } - @Getter - private final UIType UItype; + private final UIType uiType; @Getter private final Area screenArea = new Area(); @@ -66,8 +65,8 @@ public static GuiContext getDefault() { @Getter private long tick = 0; @Getter private int currentDrawingZ = 0; - public GuiContext(UIType UItype) { - this.UItype = UItype; + public GuiContext(UIType uiType) { + this.uiType = uiType; } public boolean isAbove(IWidget widget) { @@ -155,6 +154,10 @@ public void tick() { this.tick += 1; } + public UIType getUIType() { + return this.uiType; + } + /* Viewport */ public Matrix4f getLastGraphicsPose() { From 4fe41280852312858cf1052c3b08b544122b9c2a Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:04:34 -0400 Subject: [PATCH 64/84] fixed jei tooltips added through widgets not rendering --- .../core/mixins/jei/RecipeLayoutMixin.java | 44 +++++++++++++++++++ .../integration/jei/JeiRecipeViewerSlot.java | 9 ++++ 2 files changed, 53 insertions(+) diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java index f2753977..dcfa6188 100644 --- a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java @@ -5,14 +5,58 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; import mezz.jei.api.gui.IRecipeLayoutDrawable; +import mezz.jei.api.gui.widgets.IRecipeWidget; import mezz.jei.api.recipe.category.IRecipeCategory; +import mezz.jei.api.recipe.category.extensions.IRecipeCategoryDecorator; +import mezz.jei.common.gui.JeiTooltip; +import mezz.jei.common.util.ImmutableRect2i; import mezz.jei.library.gui.recipes.RecipeLayout; + +import mezz.jei.library.gui.recipes.RecipeLayoutInputHandler; + +import net.minecraft.client.gui.GuiGraphics; + +import net.minecraft.client.gui.navigation.ScreenPosition; + +import org.spongepowered.asm.mixin.Debug; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Collection; +import java.util.List; @Mixin(value = RecipeLayout.class, remap = false) public abstract class RecipeLayoutMixin implements IRecipeLayoutDrawable { + @Shadow + @Final + private IRecipeCategory recipeCategory; + + @Shadow + @Final + private List allWidgets; + + @Shadow private ImmutableRect2i area; + + @Inject(method = "drawOverlays", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;popPose()V", shift = At.Shift.AFTER), cancellable = true) + private void modularui$disableTooltipRender(GuiGraphics guiGraphics, int mouseX, int mouseY, CallbackInfo ci){ + if (this.recipeCategory instanceof ModularUIJeiCategory){ + int recipeMouseX = mouseX - this.area.getX(); + int recipeMouseY = mouseY - this.area.getY(); + JeiTooltip tooltip = new JeiTooltip(); + for(IRecipeWidget widget : this.allWidgets) { + ScreenPosition position = widget.getPosition(); + widget.getTooltip(tooltip, recipeMouseX - position.x(), recipeMouseY - position.y()); + } + tooltip.draw(guiGraphics, mouseX, mouseY); + ci.cancel(); + } + } + @ModifyExpressionValue(method = "", at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getWidth()I"), require = 0) private int modularui$makeWidthRecipeDependent(int original, @Local(argsOnly = true) IRecipeCategory recipeCategory, diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 2cf27cd4..6443fc1d 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -6,6 +6,7 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenPosition; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import brachy.modularui.screen.viewport.ModularGuiContext; @@ -56,6 +57,14 @@ public JeiRecipeViewerSlot(Class ingredientClass) { super(ingredientClass); size(18, 18); + tooltipAutoUpdate(true); + tooltipDynamic(tooltip -> { + if (slotWidget != null){ + for (Component component : slotWidget.getTooltip()){ + tooltip.addLine(component); + } + } + }); } public void setSlotWidget(IRecipeSlotDrawable slotWidget) { From dcc746b8c0d88bd5c4b760accb2559a5cff63c8a Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:56:14 +0300 Subject: [PATCH 65/84] Cleaner tooltip mixin --- .../core/mixins/jei/RecipeLayoutMixin.java | 58 ++++++------------- 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java index dcfa6188..3ae0a591 100644 --- a/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java +++ b/src/main/java/brachy/modularui/core/mixins/jei/RecipeLayoutMixin.java @@ -3,61 +3,35 @@ import brachy.modularui.integration.jei.recipe.ModularUIJeiCategory; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import mezz.jei.api.gui.IRecipeLayoutDrawable; -import mezz.jei.api.gui.widgets.IRecipeWidget; +import mezz.jei.api.gui.inputs.RecipeSlotUnderMouse; import mezz.jei.api.recipe.category.IRecipeCategory; -import mezz.jei.api.recipe.category.extensions.IRecipeCategoryDecorator; -import mezz.jei.common.gui.JeiTooltip; -import mezz.jei.common.util.ImmutableRect2i; import mezz.jei.library.gui.recipes.RecipeLayout; -import mezz.jei.library.gui.recipes.RecipeLayoutInputHandler; - -import net.minecraft.client.gui.GuiGraphics; - -import net.minecraft.client.gui.navigation.ScreenPosition; - -import org.spongepowered.asm.mixin.Debug; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Collection; -import java.util.List; +import java.util.Optional; @Mixin(value = RecipeLayout.class, remap = false) public abstract class RecipeLayoutMixin implements IRecipeLayoutDrawable { - @Shadow - @Final - private IRecipeCategory recipeCategory; - - @Shadow - @Final - private List allWidgets; - - @Shadow private ImmutableRect2i area; - - @Inject(method = "drawOverlays", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;popPose()V", shift = At.Shift.AFTER), cancellable = true) - private void modularui$disableTooltipRender(GuiGraphics guiGraphics, int mouseX, int mouseY, CallbackInfo ci){ - if (this.recipeCategory instanceof ModularUIJeiCategory){ - int recipeMouseX = mouseX - this.area.getX(); - int recipeMouseY = mouseY - this.area.getY(); - JeiTooltip tooltip = new JeiTooltip(); - for(IRecipeWidget widget : this.allWidgets) { - ScreenPosition position = widget.getPosition(); - widget.getTooltip(tooltip, recipeMouseX - position.x(), recipeMouseY - position.y()); - } - tooltip.draw(guiGraphics, mouseX, mouseY); - ci.cancel(); + @WrapOperation(method = "drawOverlays", + at = @At(value = "INVOKE", target = "Lmezz/jei/library/gui/recipes/RecipeLayout;getSlotUnderMouse(DD)Ljava/util/Optional;")) + private Optional modularui$disableIngredientTooltipRender(RecipeLayout self, double mouseX, double mouseY, + Operation> original) { + if (self.getRecipeCategory() instanceof ModularUIJeiCategory){ + return Optional.empty(); } + return original.call(self, mouseX, mouseY); } - @ModifyExpressionValue(method = "", at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getWidth()I"), require = 0) + @ModifyExpressionValue(method = "", + at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getWidth()I"), + require = 0) private int modularui$makeWidthRecipeDependent(int original, @Local(argsOnly = true) IRecipeCategory recipeCategory, @Local(argsOnly = true) R recipe) { @@ -67,7 +41,9 @@ public abstract class RecipeLayoutMixin implements IRecipeLayoutDrawable { return original; } - @ModifyExpressionValue(method = "", at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getHeight()I"), require = 0) + @ModifyExpressionValue(method = "", + at = @At(value = "INVOKE", target = "Lmezz/jei/api/recipe/category/IRecipeCategory;getHeight()I"), + require = 0) private int modularui$makeHeightRecipeDependent(int original, @Local(argsOnly = true) IRecipeCategory recipeCategory, @Local(argsOnly = true) R recipe) { From 0e0c21e1d7fc45f7d718ada12fdaea465c5e9175 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 11 Aug 2026 07:33:40 -0400 Subject: [PATCH 66/84] possibly fixed rei slot overlay and tooltip rendering --- .../integration/rei/ReiRecipeViewerSlot.java | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 7c5903af..904d22cb 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -1,7 +1,10 @@ package brachy.modularui.integration.rei; +import brachy.modularui.api.drawable.IRichTextBuilder; +import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; +import brachy.modularui.screen.RichTooltip; import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.theme.WidgetThemeEntry; @@ -9,8 +12,12 @@ import lombok.Getter; import me.shedaniel.math.Point; import me.shedaniel.rei.api.client.gui.widgets.Slot; +import me.shedaniel.rei.api.client.gui.widgets.Tooltip; +import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; import me.shedaniel.rei.api.client.gui.widgets.Widgets; +import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; @ApiStatus.Internal public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget> { @@ -21,14 +28,14 @@ public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget ingredientClass) { super(ingredientClass); - this.slotWidget = Widgets.createSlot(ONE).disableBackground(); + this.slotWidget = this.new MuiEntryWidget(ONE).disableBackground(); size(18, 18); } @Override protected void rebuildRealSlot() { - slotWidget = Widgets.createSlot(ONE).disableBackground(); + slotWidget = this.new MuiEntryWidget(ONE).disableBackground(); slotWidget.entries(REIStackConverter.convertToReiEntry(this.entries, this.chance, this.renderMappingFunction)); if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { @@ -54,4 +61,35 @@ public Result onMousePressed(int button) { public Result onKeyPressed(int keyCode, int scanCode, int modifiers) { return this.slotWidget.keyPressed(keyCode, scanCode, modifiers) ? Result.SUCCESS : Result.ACCEPT; } + + public class MuiEntryWidget extends EntryWidget{ + public MuiEntryWidget(Point point) { + super(point); + } + + @Override + public @Nullable Tooltip getTooltip(TooltipContext context) { + RichTooltip richTooltip = ReiRecipeViewerSlot.this.getTooltip(); + if (richTooltip != null){ + if (richTooltip.autoUpdate()) richTooltip.markDirty(); + richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary + + IRichTextBuilder richTextBuilder = richTooltip.getRichText(); + if (richTextBuilder instanceof RichText richText) { + // scuffed conversion, but it mostly works + Tooltip tooltipBuilder = Tooltip.create(context.getPoint()); + for (var line : richText.getAsText()) { + line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); + } + return tooltipBuilder; + } + } + return null; + } + + public boolean containsMouse(double mouseX, double mouseY) { + return mouseX >= (double)(ReiRecipeViewerSlot.this.getArea().x + 1) && mouseY >= (double)(ReiRecipeViewerSlot.this.getArea().y + 1) && mouseX <= (double)(ReiRecipeViewerSlot.this.getArea().width - 1) && mouseY <= (double)(ReiRecipeViewerSlot.this.getArea().height - 1); + } + + } } From 30afae2c19be455a600261e2078209ad85714c73 Mon Sep 17 00:00:00 2001 From: Trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 11 Aug 2026 08:49:31 -0400 Subject: [PATCH 67/84] made MuiEntryWidget private --- .../brachy/modularui/integration/rei/ReiRecipeViewerSlot.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 904d22cb..f43c8f8f 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -62,8 +62,8 @@ public Result onKeyPressed(int keyCode, int scanCode, int modifiers) { return this.slotWidget.keyPressed(keyCode, scanCode, modifiers) ? Result.SUCCESS : Result.ACCEPT; } - public class MuiEntryWidget extends EntryWidget{ - public MuiEntryWidget(Point point) { + private class MuiEntryWidget extends EntryWidget{ + private MuiEntryWidget(Point point) { super(point); } From cf3d9bf4cb8e7c6ffa874cb7f02c2439c2f191d0 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:28:42 -0400 Subject: [PATCH 68/84] changed tooltip rendering back to being handled by UIWidget, hopefully it works --- .../integration/rei/ReiRecipeViewerSlot.java | 40 ++++++++----------- .../rei/recipe/ModularUIReiDisplay.java | 4 ++ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index f43c8f8f..7ffb99a9 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -1,6 +1,8 @@ package brachy.modularui.integration.rei; import brachy.modularui.api.drawable.IRichTextBuilder; +import brachy.modularui.api.drawable.Text; +import brachy.modularui.drawable.ClientTooltipComponentIcon; import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; @@ -28,14 +30,26 @@ public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget ingredientClass) { super(ingredientClass); - this.slotWidget = this.new MuiEntryWidget(ONE).disableBackground(); - + this.slotWidget = this.new MuiEntryWidget(ONE).disableBackground().disableTooltips(); + + this.tooltipAutoUpdate(true); + this.tooltipDynamic(r -> { + if (slotWidget != null){ + Tooltip tooltip = slotWidget.getCurrentTooltip(TooltipContext.ofMouse()); + if (tooltip == null) return; + tooltip.entries().forEach(e -> { + if (e.isText()){ + r.addLine(e.getAsText()); + } + }); + } + }); size(18, 18); } @Override protected void rebuildRealSlot() { - slotWidget = this.new MuiEntryWidget(ONE).disableBackground(); + slotWidget = this.new MuiEntryWidget(ONE).disableBackground().disableTooltips(); slotWidget.entries(REIStackConverter.convertToReiEntry(this.entries, this.chance, this.renderMappingFunction)); if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { @@ -67,26 +81,6 @@ private MuiEntryWidget(Point point) { super(point); } - @Override - public @Nullable Tooltip getTooltip(TooltipContext context) { - RichTooltip richTooltip = ReiRecipeViewerSlot.this.getTooltip(); - if (richTooltip != null){ - if (richTooltip.autoUpdate()) richTooltip.markDirty(); - richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary - - IRichTextBuilder richTextBuilder = richTooltip.getRichText(); - if (richTextBuilder instanceof RichText richText) { - // scuffed conversion, but it mostly works - Tooltip tooltipBuilder = Tooltip.create(context.getPoint()); - for (var line : richText.getAsText()) { - line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); - } - return tooltipBuilder; - } - } - return null; - } - public boolean containsMouse(double mouseX, double mouseY) { return mouseX >= (double)(ReiRecipeViewerSlot.this.getArea().x + 1) && mouseY >= (double)(ReiRecipeViewerSlot.this.getArea().y + 1) && mouseX <= (double)(ReiRecipeViewerSlot.this.getArea().width - 1) && mouseY <= (double)(ReiRecipeViewerSlot.this.getArea().height - 1); } diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java index 4b4cc540..4cac5c77 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java @@ -161,6 +161,10 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTi }, () -> { screen.getContext().popMatrix(); }); + Tooltip tooltip = this.getTooltip(TooltipContext.ofMouse()); + if (tooltip != null) { + tooltip.queue(); + } } @Override From 1a05ce47b52a375ca1ef65f31f8e41404d7c8641 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:34:10 -0400 Subject: [PATCH 69/84] added code for drawing slot overlay in JeiRecipeViewerSlot, doesn't work yet --- .../integration/jei/JeiRecipeViewerSlot.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 6443fc1d..9e2bdfca 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -4,6 +4,7 @@ import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; +import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenPosition; import net.minecraft.network.chat.Component; @@ -165,6 +166,18 @@ public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { if (this.slotWidget == null) return; this.slotWidget.draw(context.getGraphics()); + if (isMouseIn(context.getMouseX(), context.getMouseY())){ + PoseStack poseStack = context.graphicsPose(); + poseStack.pushPose(); + //ScreenPosition offset = slotWidget.offset(); + //poseStack.translate((float)offset.x(), (float)offset.y(), 0.0F); + slotWidget.drawHoverOverlays(context.getGraphics()); + poseStack.popPose(); + } + } + + protected boolean isMouseIn(double mouseX, double mouseY){ + return false; //TODO } /* @Override From a13c1dc6667414eef046ed05fc4eca9ee8856ea3 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:53:36 -0400 Subject: [PATCH 70/84] fixed rei slot overlay --- .../brachy/modularui/integration/rei/ReiRecipeViewerSlot.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 7ffb99a9..16dd60ff 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -82,7 +82,7 @@ private MuiEntryWidget(Point point) { } public boolean containsMouse(double mouseX, double mouseY) { - return mouseX >= (double)(ReiRecipeViewerSlot.this.getArea().x + 1) && mouseY >= (double)(ReiRecipeViewerSlot.this.getArea().y + 1) && mouseX <= (double)(ReiRecipeViewerSlot.this.getArea().width - 1) && mouseY <= (double)(ReiRecipeViewerSlot.this.getArea().height - 1); + return ReiRecipeViewerSlot.this.isHovering(); } } From fb272b30dcca92d312db8e26eda3c22d2dab6bd6 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:55:40 -0400 Subject: [PATCH 71/84] fixed jei slot overlay rendering --- .../modularui/integration/jei/JeiRecipeViewerSlot.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 9e2bdfca..3cfb6a50 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -166,19 +166,11 @@ public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { if (this.slotWidget == null) return; this.slotWidget.draw(context.getGraphics()); - if (isMouseIn(context.getMouseX(), context.getMouseY())){ - PoseStack poseStack = context.graphicsPose(); - poseStack.pushPose(); - //ScreenPosition offset = slotWidget.offset(); - //poseStack.translate((float)offset.x(), (float)offset.y(), 0.0F); + if (isHovering()){ slotWidget.drawHoverOverlays(context.getGraphics()); - poseStack.popPose(); } } - protected boolean isMouseIn(double mouseX, double mouseY){ - return false; //TODO - } /* @Override public Result onMousePressed(int button) { From f4c8488c9359f09d25c2945e41c8f2b446572cbf Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:16:01 +0300 Subject: [PATCH 72/84] oops :3 --- src/main/java/brachy/modularui/test/TestMachine.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index e5addf0e..11590453 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -444,7 +444,6 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler inItems w.syncHandler(new FluidSlotSyncHandler(outFluids, i).canFillSlot(false)); } }) - .tank(outFluids, i) .recipeRole(RecipeSlotRole.OUTPUT))))); } } From 88a9bcb4ec3ab995080db742fcf5780f8855a535 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:17:25 +0300 Subject: [PATCH 73/84] Remove the code for drawing vanilla elements in MUI embeds because it'd always just redraw existing elements from the screen we're embedded in --- .../emi/recipe/ModularUIEmiRecipe.java | 2 +- .../brachy/modularui/screen/EmbedHandler.java | 25 ------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 8c5cedfa..2a98a190 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -200,7 +200,7 @@ public UIWrapperWidget(ModularUIEmiRecipe recipe, boolean useScreenCache) { @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - EmbedHandler.drawEmbed(this.screen, graphics, mouseX, mouseY, partialTick, r -> !(r instanceof SizedButtonWidget)); + EmbedHandler.drawEmbed(this.screen, graphics, mouseX, mouseY, partialTick); } @Override diff --git a/src/main/java/brachy/modularui/screen/EmbedHandler.java b/src/main/java/brachy/modularui/screen/EmbedHandler.java index 5bab8d36..7c229a23 100644 --- a/src/main/java/brachy/modularui/screen/EmbedHandler.java +++ b/src/main/java/brachy/modularui/screen/EmbedHandler.java @@ -28,27 +28,8 @@ public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mou drawEmbed(screen, graphics, mouseX, mouseY, partialTicks, null, null); } - public static void drawEmbedNoVanillaElements(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { - drawEmbedNoVanillaElements(screen, graphics, mouseX, mouseY, partialTicks, null, null); - } - public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks, @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { - drawEmbed(screen, graphics, mouseX, mouseY, partialTicks, r -> true, beforeDraw, afterDraw); - } - - public static void drawEmbedNoVanillaElements(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks, - @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { - drawEmbed(screen, graphics, mouseX, mouseY, partialTicks, r -> false, beforeDraw, afterDraw); - } - - public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks, - Predicate vanillaElementFilter) { - drawEmbed(screen, graphics, mouseX, mouseY, partialTicks, vanillaElementFilter, null, null); - } - - public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mouseX, int mouseY, float partialTicks, - Predicate vanillaElementFilter, @Nullable Runnable beforeDraw, @Nullable Runnable afterDraw) { screen.getContext().reset(); graphics.pose().pushPose(); @@ -58,12 +39,6 @@ public static void drawEmbed(ModularScreen screen, GuiGraphics graphics, int mou int my = screen.getContext().unTransformY(mouseX, mouseY); screen.render(graphics, mx, my, partialTicks); - if (vanillaElementFilter != null) { - RenderSystem.disableDepthTest(); - ClientScreenHandler.drawVanillaElements(graphics, screen.getScreenWrapper().wrappedScreen(), mouseX, mouseY, partialTicks, vanillaElementFilter); - RenderSystem.enableDepthTest(); - } - screen.drawForeground(graphics); if (afterDraw != null) afterDraw.run(); From 8a9624d458942a8abf3ec5d5d2beed252b92c56a Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:24:53 +0300 Subject: [PATCH 74/84] aw my cleanup thing didnt work :( --- .../brachy/modularui/test/TestMachine.java | 24 +++----------- .../modularui/test/TestRecipeViewerGuis.java | 33 ++++++++++++++++++- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/main/java/brachy/modularui/test/TestMachine.java b/src/main/java/brachy/modularui/test/TestMachine.java index 11590453..54578564 100644 --- a/src/main/java/brachy/modularui/test/TestMachine.java +++ b/src/main/java/brachy/modularui/test/TestMachine.java @@ -403,18 +403,10 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler inItems .child(Flow.col().name("input") .coverChildren() .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() - .configure(w -> { - if (!isRecipeViewerUI) { - w.slot(new ModularSlot(inItems, i)); - } - }) + .slot(new ModularSlot(inItems, i)) .recipeRole(RecipeSlotRole.INPUT))) .child(SlotGroupWidget.rect(2, 1, i -> new FluidSlot() - .configure(w -> { - if (!isRecipeViewerUI) { - w.syncHandler(new FluidSlotSyncHandler(inFluids, i)); - } - }) + .syncHandler(new FluidSlotSyncHandler(inFluids, i)) .recipeRole(RecipeSlotRole.INPUT)))) .child(new ProgressWidget() .value(progress) @@ -432,18 +424,10 @@ public static IWidget buildMachineUI(ModularPanel panel, IItemHandler inItems .child(Flow.col().name("output") .coverChildren() .child(SlotGroupWidget.rect(2, 2, i -> new ItemSlot() - .configure(w -> { - if (!isRecipeViewerUI) { - w.slot(new ModularSlot(outItems, i).canPut(false).canDragInto(false)); - } - }) + .slot(new ModularSlot(outItems, i).canPut(false).canDragInto(false)) .recipeRole(RecipeSlotRole.OUTPUT))) .child(SlotGroupWidget.rect(2, 1, i -> new FluidSlot() - .configure(w -> { - if (!isRecipeViewerUI) { - w.syncHandler(new FluidSlotSyncHandler(outFluids, i).canFillSlot(false)); - } - }) + .syncHandler(new FluidSlotSyncHandler(outFluids, i).canFillSlot(false)) .recipeRole(RecipeSlotRole.OUTPUT))))); } } diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index cbe1ec90..f536e920 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -1,5 +1,9 @@ package brachy.modularui.test; +import brachy.modularui.utils.handlers.fluid.EmptyFluidTank; +import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; +import brachy.modularui.utils.handlers.fluid.MultiTankFluidHandler; + import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraftforge.fluids.FluidStack; @@ -39,6 +43,11 @@ import mezz.jei.api.registration.IRecipeCatalystRegistration; import mezz.jei.api.registration.IRecipeCategoryRegistration; import mezz.jei.api.registration.IRecipeRegistration; + +import net.minecraftforge.fluids.IFluidTank; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.wrapper.EmptyHandler; + import org.jetbrains.annotations.NotNullByDefault; import org.jetbrains.annotations.Nullable; @@ -50,11 +59,33 @@ @NotNullByDefault public class TestRecipeViewerGuis { + private static final IItemHandler EMPTY_INFINITE_ITEM_HANDLER = new EmptyHandler() { + @Override + public int getSlots() { + return Integer.MAX_VALUE; // pls don't iterate UwU + } + }; + private static final IMultiTankFluidHandler EMPTY_MULTI_TANK_FLUID_HANDLER = new MultiTankFluidHandler() { + @Override + public int getTanks() { + return Integer.MAX_VALUE; // pls don't iterate UwU + } + + @Override + public IFluidTank getFluidTank(int index) { + return EmptyFluidTank.INSTANCE; + } + }; + public static IWidget buildViewerUI(TestMachine.Recipe recipe) { var panel = new ModularPanel<>("recipe_viewer_recipe") .coverChildren(60, 40) .invisible(); - IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, null, null, null, null, DoubleValue.simulateProgress(5000), true); + IWidget recipeUI = TestMachine.Recipes.buildMachineUI(panel, + EMPTY_INFINITE_ITEM_HANDLER, EMPTY_INFINITE_ITEM_HANDLER, + EMPTY_MULTI_TANK_FLUID_HANDLER, EMPTY_MULTI_TANK_FLUID_HANDLER, + DoubleValue.simulateProgress(5000), true); + recipeUI.visitTransformAllChildren(w -> { if (w instanceof ItemSlot slot) { List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.inItems : recipe.outItems; From 79b44d5b3db393c95c1ffc460f371405093b93fa Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:57:42 +0300 Subject: [PATCH 75/84] try to fix EMI/JEI/REI tooltip (i cant test this, sorry. laptop running out of battery.) --- .../integration/emi/EmiRecipeViewerSlot.java | 7 ---- .../emi/recipe/ModularUIEmiRecipe.java | 6 +++- .../integration/jei/JeiRecipeViewerSlot.java | 8 ----- .../jei/recipe/ModularUIJeiCategory.java | 4 +++ .../integration/rei/ReiRecipeViewerSlot.java | 36 ++----------------- .../rei/recipe/ModularUIReiDisplay.java | 7 ++++ .../modularui/test/TestRecipeViewerGuis.java | 6 +++- 7 files changed, 23 insertions(+), 51 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index 12dbd705..c6089170 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -27,13 +27,6 @@ public EmiRecipeViewerSlot(Class ingredientClass) { this.slotWidget = new SlotWidget(EmiIngredient.of(Ingredient.EMPTY), 0, 0); size(18, 18); - - tooltipAutoUpdate(true); - tooltipDynamic(tooltip -> { - for (ClientTooltipComponent ctc : this.slotWidget.getTooltip(getContext().getAbsMouseX(), getContext().getAbsMouseY())) { - tooltip.addDrawableLine(new ClientTooltipComponentIcon(ctc)); - } - }); } @Override diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 2a98a190..fbfdbf69 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -218,7 +218,11 @@ public List getTooltip(int mouseX, int mouseY) { if (richTooltip.getRichText() instanceof RichText richText) { // scuffed conversion, but it mostly works - return richText.getAsText().toClientTooltipComponents(); + List components = richText.getAsText().toClientTooltipComponents(); + if (hovered instanceof EmiRecipeViewerSlot recipeViewerSlot) { + components.addAll(0, recipeViewerSlot.getSlotWidget().getTooltip(mouseX, mouseY)); + } + return components; } } return List.of(); diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 3cfb6a50..90964a07 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -58,14 +58,6 @@ public JeiRecipeViewerSlot(Class ingredientClass) { super(ingredientClass); size(18, 18); - tooltipAutoUpdate(true); - tooltipDynamic(tooltip -> { - if (slotWidget != null){ - for (Component component : slotWidget.getTooltip()){ - tooltip.addLine(component); - } - } - }); } public void setSlotWidget(IRecipeSlotDrawable slotWidget) { diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java index 44616f00..14663a08 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java @@ -298,6 +298,10 @@ public void getTooltip(ITooltipBuilder tooltipBuilder, double mouseX, double mou IRichTextBuilder richTextBuilder = richTooltip.getRichText(); if (richTextBuilder instanceof RichText richText) { + if (hovered instanceof JeiRecipeViewerSlot recipeViewerSlot && recipeViewerSlot.getSlotWidget() != null) { + recipeViewerSlot.getSlotWidget().getTooltip(tooltipBuilder); + } + for (var line : richText.getAsText()) { // scuffed conversion, but it mostly works line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 16dd60ff..9b3cf0ab 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -1,12 +1,7 @@ package brachy.modularui.integration.rei; -import brachy.modularui.api.drawable.IRichTextBuilder; -import brachy.modularui.api.drawable.Text; -import brachy.modularui.drawable.ClientTooltipComponentIcon; -import brachy.modularui.drawable.text.RichText; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; -import brachy.modularui.screen.RichTooltip; import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.theme.WidgetThemeEntry; @@ -14,12 +9,8 @@ import lombok.Getter; import me.shedaniel.math.Point; import me.shedaniel.rei.api.client.gui.widgets.Slot; -import me.shedaniel.rei.api.client.gui.widgets.Tooltip; -import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; -import me.shedaniel.rei.api.client.gui.widgets.Widgets; import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; @ApiStatus.Internal public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget> { @@ -30,26 +21,14 @@ public class ReiRecipeViewerSlot extends RecipeViewerSlotWidget ingredientClass) { super(ingredientClass); - this.slotWidget = this.new MuiEntryWidget(ONE).disableBackground().disableTooltips(); + this.slotWidget = new EntryWidget(ONE).disableBackground().disableTooltips(); - this.tooltipAutoUpdate(true); - this.tooltipDynamic(r -> { - if (slotWidget != null){ - Tooltip tooltip = slotWidget.getCurrentTooltip(TooltipContext.ofMouse()); - if (tooltip == null) return; - tooltip.entries().forEach(e -> { - if (e.isText()){ - r.addLine(e.getAsText()); - } - }); - } - }); size(18, 18); } @Override protected void rebuildRealSlot() { - slotWidget = this.new MuiEntryWidget(ONE).disableBackground().disableTooltips(); + slotWidget = new EntryWidget(ONE).disableBackground().disableTooltips(); slotWidget.entries(REIStackConverter.convertToReiEntry(this.entries, this.chance, this.renderMappingFunction)); if (recipeSlotRole == RecipeSlotRole.INPUT || recipeSlotRole == RecipeSlotRole.CATALYST) { @@ -75,15 +54,4 @@ public Result onMousePressed(int button) { public Result onKeyPressed(int keyCode, int scanCode, int modifiers) { return this.slotWidget.keyPressed(keyCode, scanCode, modifiers) ? Result.SUCCESS : Result.ACCEPT; } - - private class MuiEntryWidget extends EntryWidget{ - private MuiEntryWidget(Point point) { - super(point); - } - - public boolean containsMouse(double mouseX, double mouseY) { - return ReiRecipeViewerSlot.this.isHovering(); - } - - } } diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java index 4cac5c77..e1582a29 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java @@ -183,6 +183,13 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTi if (richTextBuilder instanceof RichText richText) { // scuffed conversion, but it mostly works Tooltip tooltipBuilder = Tooltip.create(context.getPoint()); + if (hovered instanceof ReiRecipeViewerSlot recipeViewerSlot) { + Tooltip originalTooltip = recipeViewerSlot.getSlotWidget().getCurrentTooltip(context); + if (originalTooltip != null) { + tooltipBuilder.entries().addAll(originalTooltip.entries()); + } + } + for (var line : richText.getAsText()) { line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); } diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index f536e920..1a1d8147 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -4,6 +4,7 @@ import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; import brachy.modularui.utils.handlers.fluid.MultiTankFluidHandler; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraftforge.fluids.FluidStack; @@ -92,7 +93,10 @@ public static IWidget buildViewerUI(TestMachine.Recipe recipe) { int index = slot.getSlot().getSlotIndex(); ItemStack item = index >= l.size() ? ItemStack.EMPTY : l.get(index); return slot.toRecipeViewerSlot() - .value(item); + .value(item) + .tooltip(tooltip -> { + tooltip.newLine().add(Component.translatable("modularui.test")); + }); } else if (w instanceof FluidSlot slot) { List l = slot.getRecipeRole() == RecipeSlotRole.INPUT ? recipe.inFluids : recipe.outFluids; int index = slot.getSyncHandler().getTankIndex(); From 225807c7624e02d7a19f4af697734521510b6889 Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:58:23 +0300 Subject: [PATCH 76/84] Remove unnecessary checks in TooltipLines --- .../brachy/modularui/screen/RichTooltip.java | 5 +---- .../brachy/modularui/utils/TooltipLines.java | 17 +++-------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/brachy/modularui/screen/RichTooltip.java b/src/main/java/brachy/modularui/screen/RichTooltip.java index a03611e7..c1ccf779 100644 --- a/src/main/java/brachy/modularui/screen/RichTooltip.java +++ b/src/main/java/brachy/modularui/screen/RichTooltip.java @@ -384,10 +384,7 @@ public IRichTextBuilder getRichText() { public RichTooltip tooltipBuilder(Consumer tooltipBuilder) { Consumer existingBuilder = this.tooltipBuilder; if (existingBuilder != null && tooltipBuilder != null) { - this.tooltipBuilder = tooltip -> { - existingBuilder.accept(this); - tooltipBuilder.accept(this); - }; + this.tooltipBuilder = existingBuilder.andThen(tooltipBuilder); } else { this.tooltipBuilder = tooltipBuilder; } diff --git a/src/main/java/brachy/modularui/utils/TooltipLines.java b/src/main/java/brachy/modularui/utils/TooltipLines.java index c5e64e93..857d78e1 100755 --- a/src/main/java/brachy/modularui/utils/TooltipLines.java +++ b/src/main/java/brachy/modularui/utils/TooltipLines.java @@ -134,9 +134,6 @@ public void add(int index, Either s) { lines.get(i).index++; } s.ifLeft(ft -> { - if (!(ft instanceof Component)) { - throw new IllegalArgumentException("Tooltip text must be components"); - } this.elements.add(elementIndex, ft); this.lastElementIndex++; }); @@ -154,11 +151,6 @@ public void add(Component s) { @Override public Either set(int index, Either element) { Line line = lines.get(index); - element.ifLeft(ft -> { - if (!(ft instanceof Component)) { - throw new IllegalArgumentException("Tooltip text must be components"); - } - }); if (line.length == 1) { this.elements.set(line.index, element); this.lines.set(index, new Line(element, line.index, line.length)); @@ -183,17 +175,14 @@ public List toClientTooltipComponents() { .toList(); } - public static ClientTooltipComponent textToCTC(FormattedText text) { + public static ClientTooltipComponent textToCTC(Component text) { if (text instanceof ClientTooltipComponent ctc) return ctc; - if (text instanceof Component component) { - return ClientTooltipComponent.create(component.getVisualOrderText()); - } - return ClientTooltipComponent.create(Language.getInstance().getVisualOrder(text)); + return ClientTooltipComponent.create(text.getVisualOrderText()); } public static ClientTooltipComponent tooltipComponentToCTC(TooltipComponent comp) { - if (comp instanceof ClientTooltipComponent ctc) return ctc; if (comp instanceof ClientTooltipComponentIcon icon) return icon.getClientTooltipComponent(); + if (comp instanceof ClientTooltipComponent ctc) return ctc; return ClientTooltipComponent.create(comp); } From 140a5b21831df5c40ba20f6cb3f69d4a3518e3be Mon Sep 17 00:00:00 2001 From: screret <68943070+screret@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:00:36 +0300 Subject: [PATCH 77/84] safeguard against initing debug decorator too early --- .../recipeviewer/util/RecipeDebugDecoratorUtil.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java index 68808357..7b553672 100644 --- a/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java +++ b/src/main/java/brachy/modularui/integration/recipeviewer/util/RecipeDebugDecoratorUtil.java @@ -27,6 +27,10 @@ public static void addRecipeDebugOverlays(ModularScreen screen) { return; } IMuiScreen screenWrapper = panel.getScreen().getScreenWrapper(); + if (screenWrapper.wrappedScreen() == null) { + // safeguard against initing too early + return; + } ModularScreen overlay = new DebugOverlay(screenWrapper); // set this ASAP to avoid the possibility of double init From 06902ce56c65abe60e4c4833ba3fcadf98368ccc Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Wed, 12 Aug 2026 07:06:42 -0400 Subject: [PATCH 78/84] Revert "try to fix EMI/JEI/REI tooltip" This reverts commit 79b44d5b3db393c95c1ffc460f371405093b93fa. Reverted since the previous method of adding the tooltips made it possible to modify said tooltips from the widget --- .../integration/emi/EmiRecipeViewerSlot.java | 7 +++++++ .../integration/emi/recipe/ModularUIEmiRecipe.java | 6 +----- .../integration/jei/JeiRecipeViewerSlot.java | 8 ++++++++ .../jei/recipe/ModularUIJeiCategory.java | 4 ---- .../integration/rei/ReiRecipeViewerSlot.java | 14 ++++++++++++++ .../rei/recipe/ModularUIReiDisplay.java | 7 ------- 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java index c6089170..12dbd705 100644 --- a/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/emi/EmiRecipeViewerSlot.java @@ -27,6 +27,13 @@ public EmiRecipeViewerSlot(Class ingredientClass) { this.slotWidget = new SlotWidget(EmiIngredient.of(Ingredient.EMPTY), 0, 0); size(18, 18); + + tooltipAutoUpdate(true); + tooltipDynamic(tooltip -> { + for (ClientTooltipComponent ctc : this.slotWidget.getTooltip(getContext().getAbsMouseX(), getContext().getAbsMouseY())) { + tooltip.addDrawableLine(new ClientTooltipComponentIcon(ctc)); + } + }); } @Override diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index fbfdbf69..2a98a190 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -218,11 +218,7 @@ public List getTooltip(int mouseX, int mouseY) { if (richTooltip.getRichText() instanceof RichText richText) { // scuffed conversion, but it mostly works - List components = richText.getAsText().toClientTooltipComponents(); - if (hovered instanceof EmiRecipeViewerSlot recipeViewerSlot) { - components.addAll(0, recipeViewerSlot.getSlotWidget().getTooltip(mouseX, mouseY)); - } - return components; + return richText.getAsText().toClientTooltipComponents(); } } return List.of(); diff --git a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java index 90964a07..3cfb6a50 100644 --- a/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/jei/JeiRecipeViewerSlot.java @@ -58,6 +58,14 @@ public JeiRecipeViewerSlot(Class ingredientClass) { super(ingredientClass); size(18, 18); + tooltipAutoUpdate(true); + tooltipDynamic(tooltip -> { + if (slotWidget != null){ + for (Component component : slotWidget.getTooltip()){ + tooltip.addLine(component); + } + } + }); } public void setSlotWidget(IRecipeSlotDrawable slotWidget) { diff --git a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java index 14663a08..44616f00 100644 --- a/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java +++ b/src/main/java/brachy/modularui/integration/jei/recipe/ModularUIJeiCategory.java @@ -298,10 +298,6 @@ public void getTooltip(ITooltipBuilder tooltipBuilder, double mouseX, double mou IRichTextBuilder richTextBuilder = richTooltip.getRichText(); if (richTextBuilder instanceof RichText richText) { - if (hovered instanceof JeiRecipeViewerSlot recipeViewerSlot && recipeViewerSlot.getSlotWidget() != null) { - recipeViewerSlot.getSlotWidget().getTooltip(tooltipBuilder); - } - for (var line : richText.getAsText()) { // scuffed conversion, but it mostly works line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index 9b3cf0ab..ade4e775 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -9,6 +9,8 @@ import lombok.Getter; import me.shedaniel.math.Point; import me.shedaniel.rei.api.client.gui.widgets.Slot; +import me.shedaniel.rei.api.client.gui.widgets.Tooltip; +import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; import org.jetbrains.annotations.ApiStatus; @@ -23,6 +25,18 @@ public ReiRecipeViewerSlot(Class ingredientClass) { super(ingredientClass); this.slotWidget = new EntryWidget(ONE).disableBackground().disableTooltips(); + this.tooltipAutoUpdate(true); + this.tooltipDynamic(r -> { + if (slotWidget != null){ + Tooltip tooltip = slotWidget.getCurrentTooltip(TooltipContext.ofMouse()); + if (tooltip == null) return; + tooltip.entries().forEach(e -> { + if (e.isText()){ + r.addLine(e.getAsText()); + } + }); + } + }); size(18, 18); } diff --git a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java index e1582a29..4cac5c77 100644 --- a/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java +++ b/src/main/java/brachy/modularui/integration/rei/recipe/ModularUIReiDisplay.java @@ -183,13 +183,6 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTi if (richTextBuilder instanceof RichText richText) { // scuffed conversion, but it mostly works Tooltip tooltipBuilder = Tooltip.create(context.getPoint()); - if (hovered instanceof ReiRecipeViewerSlot recipeViewerSlot) { - Tooltip originalTooltip = recipeViewerSlot.getSlotWidget().getCurrentTooltip(context); - if (originalTooltip != null) { - tooltipBuilder.entries().addAll(originalTooltip.entries()); - } - } - for (var line : richText.getAsText()) { line.ifLeft(tooltipBuilder::add).ifRight(tooltipBuilder::add); } From 1ab278326bbd65188290876c00f1edc9c35eaa29 Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Wed, 12 Aug 2026 07:26:06 -0400 Subject: [PATCH 79/84] better method of rendering rei slot overlay possibly? --- .../core/mixins/rei/EntryWidgetAccessor.java | 15 ++++ .../integration/rei/ReiRecipeViewerSlot.java | 4 ++ src/main/resources/modularui.mixins.json | 71 ++++++++++--------- 3 files changed, 55 insertions(+), 35 deletions(-) create mode 100644 src/main/java/brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java diff --git a/src/main/java/brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java b/src/main/java/brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java new file mode 100644 index 00000000..0ac91ec3 --- /dev/null +++ b/src/main/java/brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java @@ -0,0 +1,15 @@ +package brachy.modularui.core.mixins.rei; + +import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; + +import net.minecraft.client.gui.GuiGraphics; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(EntryWidget.class) +public interface EntryWidgetAccessor { + + @Invoker("drawHighlighted") + void modularui$invokeDrawHighlighted(GuiGraphics graphics, int mouseX, int mouseY, float delta); +} diff --git a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java index ade4e775..d7d55194 100644 --- a/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java +++ b/src/main/java/brachy/modularui/integration/rei/ReiRecipeViewerSlot.java @@ -1,5 +1,6 @@ package brachy.modularui.integration.rei; +import brachy.modularui.core.mixins.rei.EntryWidgetAccessor; import brachy.modularui.integration.recipeviewer.RecipeSlotRole; import brachy.modularui.integration.recipeviewer.RecipeViewerSlotWidget; import brachy.modularui.screen.viewport.ModularGuiContext; @@ -57,6 +58,9 @@ protected void rebuildRealSlot() { @Override public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { this.slotWidget.render(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); + if (this.isHovering() && slotWidget instanceof EntryWidgetAccessor accessor){ + accessor.modularui$invokeDrawHighlighted(context.getGraphics(), context.getMouseX(), context.getMouseY(), context.getRenderPartialTicks()); + } } @Override diff --git a/src/main/resources/modularui.mixins.json b/src/main/resources/modularui.mixins.json index 2ba99e4b..f25c497d 100755 --- a/src/main/resources/modularui.mixins.json +++ b/src/main/resources/modularui.mixins.json @@ -1,37 +1,38 @@ { - "required": true, - "minVersion": "0.8", - "refmap": "modularui.refmap.json", - "package": "brachy.modularui.core.mixins", - "compatibilityLevel": "JAVA_17", - "plugin": "brachy.modularui.core.ModularUIMixinPlugin", - "client": [ - "client.AbstractContainerScreenAccessor", - "client.AbstractContainerScreenMixin", - "client.MinecraftMixin", - "client.ScreenAccessor", - "client.StringSplitterAccessor", - "emi.EmiRenderHelperMixin", - "emi.RecipeScreenMixin", - "jei.IngredientListOverlayAccessor", - "rei.DefaultDisplayViewingScreenMixin" - ], - "mixins": [ - "common.AbstractContainerMenuAccessor", - "common.CombinedInvWrapperAccessor", - "common.ComponentMixin", - "common.ComponentSerializerMixin", - "common.SlotAccessor", - "common.TextColorMixin", - "jei.RecipeGuiLogicMixin", - "jei.RecipeLayoutAccessor", - "jei.RecipeLayoutBuilderAccessor", - "jei.RecipeLayoutBuilderMixin", - "jei.RecipeLayoutMixin", - "jei.RecipeSlotAccessor" - ], - "injectors": { - "defaultRequire": 1, - "maxShiftBy": 5 - } + "required": true, + "minVersion": "0.8", + "refmap": "modularui.refmap.json", + "package": "brachy.modularui.core.mixins", + "compatibilityLevel": "JAVA_17", + "plugin": "brachy.modularui.core.ModularUIMixinPlugin", + "client": [ + "client.AbstractContainerScreenAccessor", + "client.AbstractContainerScreenMixin", + "client.MinecraftMixin", + "client.ScreenAccessor", + "client.StringSplitterAccessor", + "emi.EmiRenderHelperMixin", + "emi.RecipeScreenMixin", + "jei.IngredientListOverlayAccessor", + "rei.DefaultDisplayViewingScreenMixin", + "rei.EntryWidgetAccessor" + ], + "mixins": [ + "common.AbstractContainerMenuAccessor", + "common.CombinedInvWrapperAccessor", + "common.ComponentMixin", + "common.ComponentSerializerMixin", + "common.SlotAccessor", + "common.TextColorMixin", + "jei.RecipeGuiLogicMixin", + "jei.RecipeLayoutAccessor", + "jei.RecipeLayoutBuilderAccessor", + "jei.RecipeLayoutBuilderMixin", + "jei.RecipeLayoutMixin", + "jei.RecipeSlotAccessor" + ], + "injectors": { + "defaultRequire": 1, + "maxShiftBy": 5 + } } From 651ba7dd6540a0e37c8f31c0f3dd707323359efe Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Wed, 12 Aug 2026 21:32:47 -0400 Subject: [PATCH 80/84] added test fluid tooltips for debugging emi tooltip issue --- .../brachy/modularui/test/TestRecipeViewerGuis.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java index 1a1d8147..efe5940a 100644 --- a/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java +++ b/src/main/java/brachy/modularui/test/TestRecipeViewerGuis.java @@ -4,9 +4,11 @@ import brachy.modularui.utils.handlers.fluid.IMultiTankFluidHandler; import brachy.modularui.utils.handlers.fluid.MultiTankFluidHandler; +import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; +import net.minecraftforge.common.Tags; import net.minecraftforge.fluids.FluidStack; import brachy.modularui.ModularUI; @@ -102,7 +104,13 @@ public static IWidget buildViewerUI(TestMachine.Recipe recipe) { int index = slot.getSyncHandler().getTankIndex(); FluidStack fluid = index < 0 || index >= l.size() ? FluidStack.EMPTY : l.get(index); return slot.toRecipeViewerSlot() - .value(fluid); + .value(fluid) + .tooltipBuilder(tooltip -> { + tooltip.addLine(Component.literal("Amount: " + fluid.getAmount() + " L").withStyle(ChatFormatting.BLUE)); + tooltip.addLine(Component.literal("Temperature: " + fluid.getFluid().getFluidType().getTemperature() + " K").withStyle(ChatFormatting.RED)); + String liquid = !fluid.getFluid().is(Tags.Fluids.GASEOUS) ? "liquid" : "gas"; + tooltip.addLine(Component.literal("State: " + liquid).withStyle(ChatFormatting.GREEN)); + }); } else if (w instanceof IngredientProvider slot) { return slot.toRecipeViewerSlot(); } From 7eeb2a2a5a425ee81f33becafc35e17a6b6978bf Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Thu, 13 Aug 2026 06:53:40 -0400 Subject: [PATCH 81/84] used remap=false for rei mixin --- .../brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java b/src/main/java/brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java index 0ac91ec3..495f1187 100644 --- a/src/main/java/brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java +++ b/src/main/java/brachy/modularui/core/mixins/rei/EntryWidgetAccessor.java @@ -10,6 +10,6 @@ @Mixin(EntryWidget.class) public interface EntryWidgetAccessor { - @Invoker("drawHighlighted") + @Invoker(value = "drawHighlighted", remap = false) void modularui$invokeDrawHighlighted(GuiGraphics graphics, int mouseX, int mouseY, float delta); } From 82cd7fc34879cc1eba812dfa62840cd790c9e59f Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Thu, 13 Aug 2026 07:12:00 -0400 Subject: [PATCH 82/84] changes for debugging purposes - revert once the issue is solved --- .../emi/recipe/ModularUIEmiRecipe.java | 5 ++- .../brachy/modularui/screen/RichTooltip.java | 2 +- .../brachy/modularui/utils/TooltipLines.java | 31 ++++++++++++++----- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 2a98a190..0590f6f5 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -11,6 +11,8 @@ import brachy.modularui.screen.ModularScreen; import brachy.modularui.screen.RichTooltip; +import brachy.modularui.utils.TooltipLines; + import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.resources.ResourceLocation; @@ -218,7 +220,8 @@ public List getTooltip(int mouseX, int mouseY) { if (richTooltip.getRichText() instanceof RichText richText) { // scuffed conversion, but it mostly works - return richText.getAsText().toClientTooltipComponents(); + TooltipLines test = richText.getAsText(); + return test.toClientTooltipComponents(); } } return List.of(); diff --git a/src/main/java/brachy/modularui/screen/RichTooltip.java b/src/main/java/brachy/modularui/screen/RichTooltip.java index c1ccf779..ef29a7b6 100644 --- a/src/main/java/brachy/modularui/screen/RichTooltip.java +++ b/src/main/java/brachy/modularui/screen/RichTooltip.java @@ -168,7 +168,7 @@ public void draw(GuiContext context, ItemStack stack) { // convert the List> to the type vanilla wants. Technically it's already valid, // but some mods (looking at you REI) want Components that they do nothing with except downcast them into FormattedText anyway. @SuppressWarnings("unchecked") - List> tooltipElements = (List>) (List) textLines; + List> tooltipElements = (List>) (List) textLines.toList(); // noinspection UnstableApiUsage var vanillaGatherEvent = new RenderTooltipEvent.GatherComponents(stack, screen.width, screen.height, tooltipElements, this.maxWidth); if (MinecraftForge.EVENT_BUS.post(vanillaGatherEvent)) return; diff --git a/src/main/java/brachy/modularui/utils/TooltipLines.java b/src/main/java/brachy/modularui/utils/TooltipLines.java index 857d78e1..070e311b 100755 --- a/src/main/java/brachy/modularui/utils/TooltipLines.java +++ b/src/main/java/brachy/modularui/utils/TooltipLines.java @@ -13,14 +13,18 @@ import net.minecraft.world.inventory.tooltip.TooltipComponent; import com.mojang.datafixers.util.Either; +import org.jetbrains.annotations.NotNull; + import java.util.AbstractList; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.function.Consumer; /** * A list that lazily parses a list of text-like and drawable types into a vanilla compatible types. */ -public class TooltipLines extends AbstractList> { +public class TooltipLines implements Iterable> { private final List elements; private final List lines = new ArrayList<>(8); @@ -95,19 +99,19 @@ private Line parseNext() { return null; } - @Override + //@Override public Either get(int index) { buildUntil(index); return lines.get(index).text; } - @Override + //@Override public int size() { buildUntil(Integer.MAX_VALUE); return lines.size(); } - @Override + //@Override public Either remove(int index) { buildUntil(index); Line line = lines.remove(index); @@ -125,7 +129,7 @@ public Either remove(int index) { return line.text; } - @Override + //@Override public void add(int index, Either s) { buildUntil(index); int elementIndex = index >= this.lines.size() ? this.lastElementIndex : this.lines.get(index).index; @@ -148,7 +152,7 @@ public void add(Component s) { add(size(), Either.left(s)); } - @Override + //@Override public Either set(int index, Either element) { Line line = lines.get(index); if (line.length == 1) { @@ -161,7 +165,7 @@ public Either set(int index, Either toClientTooltipComponents() { buildUntil(Integer.MAX_VALUE); - return stream() + return lines.stream().map(l -> l.text) .map(either -> either.map(TooltipLines::textToCTC, TooltipLines::tooltipComponentToCTC)) .toList(); } @@ -192,6 +196,17 @@ private static Component collapse(List components) { else return Text.comp(components.toArray(Component[]::new)); } + + + @Override + public @NotNull Iterator> iterator() { + return toList().iterator(); + } + + public List> toList(){ + return new ArrayList<>(lines.stream().map(l -> l.text).toList()); + } + private static class Line { private final Either text; From 4e2961817bc9e3a55ee8af7facf41e682c649e7d Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:04:48 -0400 Subject: [PATCH 83/84] fixed Tooltip lines skipping every other line --- src/main/java/brachy/modularui/utils/TooltipLines.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/brachy/modularui/utils/TooltipLines.java b/src/main/java/brachy/modularui/utils/TooltipLines.java index 070e311b..07e3f27c 100755 --- a/src/main/java/brachy/modularui/utils/TooltipLines.java +++ b/src/main/java/brachy/modularui/utils/TooltipLines.java @@ -60,7 +60,7 @@ private Line parseNext() { continue; } Line line = new Line(collapse(currentLine), this.lastElementIndex, currentLength); - this.lastElementIndex += currentLength; + this.lastElementIndex = i + 1; return line; } Component c = null; From 2ad80ef94c6dd197c56df74d0da15be7e94641fe Mon Sep 17 00:00:00 2001 From: trinsdar <30245301+Trinsdar@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:05:20 -0400 Subject: [PATCH 84/84] Revert "changes for debugging purposes - revert once the issue is solved" This reverts commit 82cd7fc34879cc1eba812dfa62840cd790c9e59f. --- .../emi/recipe/ModularUIEmiRecipe.java | 5 +-- .../brachy/modularui/screen/RichTooltip.java | 2 +- .../brachy/modularui/utils/TooltipLines.java | 31 +++++-------------- 3 files changed, 10 insertions(+), 28 deletions(-) diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index 0590f6f5..2a98a190 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -11,8 +11,6 @@ import brachy.modularui.screen.ModularScreen; import brachy.modularui.screen.RichTooltip; -import brachy.modularui.utils.TooltipLines; - import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.resources.ResourceLocation; @@ -220,8 +218,7 @@ public List getTooltip(int mouseX, int mouseY) { if (richTooltip.getRichText() instanceof RichText richText) { // scuffed conversion, but it mostly works - TooltipLines test = richText.getAsText(); - return test.toClientTooltipComponents(); + return richText.getAsText().toClientTooltipComponents(); } } return List.of(); diff --git a/src/main/java/brachy/modularui/screen/RichTooltip.java b/src/main/java/brachy/modularui/screen/RichTooltip.java index ef29a7b6..c1ccf779 100644 --- a/src/main/java/brachy/modularui/screen/RichTooltip.java +++ b/src/main/java/brachy/modularui/screen/RichTooltip.java @@ -168,7 +168,7 @@ public void draw(GuiContext context, ItemStack stack) { // convert the List> to the type vanilla wants. Technically it's already valid, // but some mods (looking at you REI) want Components that they do nothing with except downcast them into FormattedText anyway. @SuppressWarnings("unchecked") - List> tooltipElements = (List>) (List) textLines.toList(); + List> tooltipElements = (List>) (List) textLines; // noinspection UnstableApiUsage var vanillaGatherEvent = new RenderTooltipEvent.GatherComponents(stack, screen.width, screen.height, tooltipElements, this.maxWidth); if (MinecraftForge.EVENT_BUS.post(vanillaGatherEvent)) return; diff --git a/src/main/java/brachy/modularui/utils/TooltipLines.java b/src/main/java/brachy/modularui/utils/TooltipLines.java index 07e3f27c..c0ec7f6c 100755 --- a/src/main/java/brachy/modularui/utils/TooltipLines.java +++ b/src/main/java/brachy/modularui/utils/TooltipLines.java @@ -13,18 +13,14 @@ import net.minecraft.world.inventory.tooltip.TooltipComponent; import com.mojang.datafixers.util.Either; -import org.jetbrains.annotations.NotNull; - import java.util.AbstractList; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import java.util.function.Consumer; /** * A list that lazily parses a list of text-like and drawable types into a vanilla compatible types. */ -public class TooltipLines implements Iterable> { +public class TooltipLines extends AbstractList> { private final List elements; private final List lines = new ArrayList<>(8); @@ -99,19 +95,19 @@ private Line parseNext() { return null; } - //@Override + @Override public Either get(int index) { buildUntil(index); return lines.get(index).text; } - //@Override + @Override public int size() { buildUntil(Integer.MAX_VALUE); return lines.size(); } - //@Override + @Override public Either remove(int index) { buildUntil(index); Line line = lines.remove(index); @@ -129,7 +125,7 @@ public Either remove(int index) { return line.text; } - //@Override + @Override public void add(int index, Either s) { buildUntil(index); int elementIndex = index >= this.lines.size() ? this.lastElementIndex : this.lines.get(index).index; @@ -152,7 +148,7 @@ public void add(Component s) { add(size(), Either.left(s)); } - //@Override + @Override public Either set(int index, Either element) { Line line = lines.get(index); if (line.length == 1) { @@ -165,7 +161,7 @@ public Either set(int index, Either toClientTooltipComponents() { buildUntil(Integer.MAX_VALUE); - return lines.stream().map(l -> l.text) + return stream() .map(either -> either.map(TooltipLines::textToCTC, TooltipLines::tooltipComponentToCTC)) .toList(); } @@ -196,17 +192,6 @@ private static Component collapse(List components) { else return Text.comp(components.toArray(Component[]::new)); } - - - @Override - public @NotNull Iterator> iterator() { - return toList().iterator(); - } - - public List> toList(){ - return new ArrayList<>(lines.stream().map(l -> l.text).toList()); - } - private static class Line { private final Either text;