Skip to content

Commit 7b71ce6

Browse files
committed
Show a placeholder error recipe when a recipe layout crashes
1 parent 63e9d92 commit 7b71ce6

File tree

10 files changed

+291
-8
lines changed

10 files changed

+291
-8
lines changed

Common/src/main/java/mezz/jei/common/util/ImmutableRect2i.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.common.base.Preconditions;
44
import net.minecraft.client.gui.navigation.ScreenPosition;
5+
import net.minecraft.client.gui.navigation.ScreenRectangle;
56
import net.minecraft.client.renderer.Rect2i;
67

78
import javax.annotation.Nonnegative;
@@ -288,6 +289,10 @@ public Rect2i toMutable() {
288289
return new Rect2i(x, y, width, height);
289290
}
290291

292+
public ScreenRectangle toScreenRectangle() {
293+
return new ScreenRectangle(x, y, width, height);
294+
}
295+
291296
public ImmutablePoint2i getPosition() {
292297
return new ImmutablePoint2i(x, y);
293298
}

Common/src/main/java/mezz/jei/common/util/StringUtil.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.Collection;
1515
import java.util.List;
1616
import java.util.stream.Collectors;
17+
import java.util.stream.Stream;
1718

1819
public final class StringUtil {
1920
private StringUtil() {
@@ -48,7 +49,12 @@ public static List<FormattedText> splitLines(List<FormattedText> lines, int widt
4849
Font font = minecraft.font;
4950
StringSplitter splitter = font.getSplitter();
5051
return lines.stream()
51-
.flatMap(text -> splitter.splitLines(text, width, Style.EMPTY).stream())
52+
.flatMap(text -> {
53+
if (text.getString().isEmpty()) {
54+
return Stream.of(text);
55+
}
56+
return splitter.splitLines(text, width, Style.EMPTY).stream();
57+
})
5258
.toList();
5359
}
5460

Common/src/main/resources/assets/jei/lang/en_us.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
"gui.jei.category.registry.item": "Item",
181181
"gui.jei.category.registry.fluid": "Fluid",
182182
"gui.jei.category.tagInformation": "%s Tags",
183+
"gui.jei.category.recipe.crashed": "This recipe crashed. Please see client logs for details.",
183184

184185
"_comment": "Messages",
185186
"jei.message.configured": "Install the \"Configured\" mod to access the in-game config",

CommonApi/src/main/java/mezz/jei/api/helpers/IGuiHelper.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ default IDrawable createDrawableItemLike(ItemLike itemLike) {
193193
@Deprecated(since = "19.18.9", forRemoval = true)
194194
IScrollBoxWidget createScrollBoxWidget(IDrawable contents, int visibleHeight, int xPos, int yPos);
195195

196+
/**
197+
* Create a scroll box widget.
198+
* Handles displaying drawable contents in a scrolling area.
199+
*
200+
* @since 19.18.10
201+
*/
202+
IScrollBoxWidget createScrollBoxWidget(int width, int height, int xPos, int yPos);
203+
196204
/**
197205
* The amount of extra horizontal space that a {@link IScrollBoxWidget} takes up with its scroll bar.
198206
*

Gui/src/main/java/mezz/jei/gui/elements/GuiIconToggleButton.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public abstract class GuiIconToggleButton {
2222
public GuiIconToggleButton(IDrawable offIcon, IDrawable onIcon) {
2323
this.offIcon = offIcon;
2424
this.onIcon = onIcon;
25-
this.button = new GuiIconButton(new DrawableBlank(0, 0), b -> {});
25+
this.button = new GuiIconButton(DrawableBlank.EMPTY, b -> {});
2626
this.area = ImmutableRect2i.EMPTY;
2727
}
2828

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package mezz.jei.gui.input.handlers;
2+
3+
import com.mojang.blaze3d.platform.InputConstants;
4+
import mezz.jei.api.gui.inputs.IJeiInputHandler;
5+
import mezz.jei.api.gui.inputs.IJeiUserInput;
6+
import mezz.jei.common.util.MathUtil;
7+
import net.minecraft.client.gui.navigation.ScreenPosition;
8+
import net.minecraft.client.gui.navigation.ScreenRectangle;
9+
10+
import java.util.function.Supplier;
11+
12+
public class OffsetJeiInputHandler implements IJeiInputHandler {
13+
private final IJeiInputHandler inputHandler;
14+
private final Supplier<ScreenPosition> offset;
15+
16+
public OffsetJeiInputHandler(IJeiInputHandler inputHandler, Supplier<ScreenPosition> offset) {
17+
this.inputHandler = inputHandler;
18+
this.offset = offset;
19+
}
20+
21+
@Override
22+
public boolean handleInput(double mouseX, double mouseY, IJeiUserInput input) {
23+
ScreenPosition screenPosition = offset.get();
24+
final double offsetMouseX = mouseX - screenPosition.x();
25+
final double offsetMouseY = mouseY - screenPosition.y();
26+
27+
ScreenRectangle originalArea = inputHandler.getArea();
28+
if (MathUtil.contains(originalArea, offsetMouseX, offsetMouseY)) {
29+
ScreenPosition position = originalArea.position();
30+
double relativeMouseX = offsetMouseX - position.x();
31+
double relativeMouseY = offsetMouseY - position.y();
32+
return inputHandler.handleInput(relativeMouseX, relativeMouseY, input);
33+
}
34+
35+
return false;
36+
}
37+
38+
@Override
39+
public boolean handleMouseScrolled(double mouseX, double mouseY, double scrollDeltaX, double scrollDeltaY) {
40+
ScreenPosition screenPosition = offset.get();
41+
final double offsetMouseX = mouseX - screenPosition.x();
42+
final double offsetMouseY = mouseY - screenPosition.y();
43+
44+
ScreenRectangle originalArea = inputHandler.getArea();
45+
if (MathUtil.contains(originalArea, offsetMouseX, offsetMouseY)) {
46+
ScreenPosition position = originalArea.position();
47+
double relativeMouseX = offsetMouseX - position.x();
48+
double relativeMouseY = offsetMouseY - position.y();
49+
return inputHandler.handleMouseScrolled(relativeMouseX, relativeMouseY, scrollDeltaX, scrollDeltaY);
50+
}
51+
52+
return false;
53+
}
54+
55+
@Override
56+
public boolean handleMouseDragged(double mouseX, double mouseY, InputConstants.Key mouseKey, double dragX, double dragY) {
57+
ScreenPosition screenPosition = offset.get();
58+
final double offsetMouseX = mouseX - screenPosition.x();
59+
final double offsetMouseY = mouseY - screenPosition.y();
60+
61+
ScreenRectangle originalArea = inputHandler.getArea();
62+
if (MathUtil.contains(originalArea, offsetMouseX, offsetMouseY)) {
63+
ScreenPosition position = originalArea.position();
64+
double relativeMouseX = offsetMouseX - position.x();
65+
double relativeMouseY = offsetMouseY - position.y();
66+
return inputHandler.handleMouseDragged(relativeMouseX, relativeMouseY, mouseKey, dragX, dragY);
67+
}
68+
69+
return false;
70+
}
71+
72+
@Override
73+
public void handleMouseMoved(double mouseX, double mouseY) {
74+
ScreenPosition screenPosition = offset.get();
75+
final double offsetMouseX = mouseX - screenPosition.x();
76+
final double offsetMouseY = mouseY - screenPosition.y();
77+
78+
ScreenRectangle originalArea = inputHandler.getArea();
79+
if (MathUtil.contains(originalArea, offsetMouseX, offsetMouseY)) {
80+
ScreenPosition position = originalArea.position();
81+
double relativeMouseX = offsetMouseX - position.x();
82+
double relativeMouseY = offsetMouseY - position.y();
83+
inputHandler.handleMouseMoved(relativeMouseX, relativeMouseY);
84+
}
85+
}
86+
87+
@Override
88+
public ScreenRectangle getArea() {
89+
ScreenRectangle area = inputHandler.getArea();
90+
return new ScreenRectangle(offset.get(), area.width(), area.height());
91+
}
92+
}

Gui/src/main/java/mezz/jei/gui/recipes/RecipeGuiLogic.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import mezz.jei.common.config.IJeiClientConfigs;
1414
import mezz.jei.common.config.RecipeSorterStage;
1515
import mezz.jei.common.gui.elements.DrawableBlank;
16+
import mezz.jei.common.gui.elements.DrawableNineSliceTexture;
1617
import mezz.jei.common.util.MathUtil;
1718
import mezz.jei.gui.recipes.layouts.IRecipeLayoutList;
19+
import mezz.jei.gui.recipes.layouts.RecipeLayoutDrawableErrored;
1820
import mezz.jei.gui.recipes.lookups.IFocusedRecipes;
1921
import mezz.jei.gui.recipes.lookups.ILookupState;
2022
import mezz.jei.gui.recipes.lookups.IngredientLookupState;
@@ -251,13 +253,20 @@ private <T> IRecipeLayoutList createRecipeLayoutsWithButtons(
251253
List<T> brokenRecipes = new ArrayList<>();
252254

253255
List<RecipeLayoutWithButtons<T>> results = recipes.stream()
254-
.<IRecipeLayoutDrawable<T>>mapMulti((recipe, acceptor) -> {
256+
.map(recipe -> {
255257
if (recipeCategory.needsRecipeBorder()) {
256-
recipeManager.createRecipeLayoutDrawable(recipeCategory, recipe, state.getFocuses())
257-
.ifPresentOrElse(acceptor, () -> brokenRecipes.add(recipe));
258+
DrawableNineSliceTexture recipeBackground = Internal.getTextures().getRecipeBackground();
259+
return recipeManager.createRecipeLayoutDrawable(recipeCategory, recipe, state.getFocuses(), recipeBackground, 4)
260+
.orElseGet(() -> {
261+
brokenRecipes.add(recipe);
262+
return new RecipeLayoutDrawableErrored<>(recipeCategory, recipe, recipeBackground, 4);
263+
});
258264
} else {
259-
recipeManager.createRecipeLayoutDrawable(recipeCategory, recipe, state.getFocuses(), new DrawableBlank(0, 0), 0)
260-
.ifPresentOrElse(acceptor, () -> brokenRecipes.add(recipe));
265+
return recipeManager.createRecipeLayoutDrawable(recipeCategory, recipe, state.getFocuses(), DrawableBlank.EMPTY, 0)
266+
.orElseGet(() -> {
267+
brokenRecipes.add(recipe);
268+
return new RecipeLayoutDrawableErrored<>(recipeCategory, recipe, DrawableBlank.EMPTY, 0);
269+
});
261270
}
262271
})
263272
.map(recipeLayoutFactory::create)
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package mezz.jei.gui.recipes.layouts;
2+
3+
import com.mojang.blaze3d.vertex.PoseStack;
4+
import mezz.jei.api.gui.IRecipeLayoutDrawable;
5+
import mezz.jei.api.gui.drawable.IScalableDrawable;
6+
import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable;
7+
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
8+
import mezz.jei.api.gui.inputs.IJeiInputHandler;
9+
import mezz.jei.api.gui.inputs.RecipeSlotUnderMouse;
10+
import mezz.jei.api.gui.widgets.IScrollBoxWidget;
11+
import mezz.jei.api.helpers.IGuiHelper;
12+
import mezz.jei.api.helpers.IJeiHelpers;
13+
import mezz.jei.api.ingredients.IIngredientType;
14+
import mezz.jei.api.recipe.category.IRecipeCategory;
15+
import mezz.jei.api.runtime.IJeiRuntime;
16+
import mezz.jei.common.Internal;
17+
import mezz.jei.common.util.ImmutableRect2i;
18+
import mezz.jei.gui.input.handlers.OffsetJeiInputHandler;
19+
import net.minecraft.ChatFormatting;
20+
import net.minecraft.client.gui.GuiGraphics;
21+
import net.minecraft.client.gui.navigation.ScreenPosition;
22+
import net.minecraft.client.renderer.Rect2i;
23+
import net.minecraft.network.chat.Component;
24+
import net.minecraft.network.chat.FormattedText;
25+
import net.minecraft.resources.ResourceLocation;
26+
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
import java.util.Optional;
30+
31+
public class RecipeLayoutDrawableErrored<R> implements IRecipeLayoutDrawable<R> {
32+
private final IRecipeCategory<R> recipeCategory;
33+
private final R recipe;
34+
private final IScrollBoxWidget scrollBoxWidget;
35+
private final IJeiInputHandler inputHandler;
36+
private final IScalableDrawable background;
37+
private final int borderPadding;
38+
private ImmutableRect2i area;
39+
40+
public RecipeLayoutDrawableErrored(IRecipeCategory<R> recipeCategory, R recipe, IScalableDrawable background, int borderPadding) {
41+
this.recipeCategory = recipeCategory;
42+
this.recipe = recipe;
43+
this.area = new ImmutableRect2i(0, 0, Math.max(100, recipeCategory.getWidth()), recipeCategory.getHeight());
44+
this.background = background;
45+
this.borderPadding = borderPadding;
46+
47+
List<FormattedText> lines = new ArrayList<>();
48+
lines.add(Component.translatable("gui.jei.category.recipe.crashed").withStyle(ChatFormatting.RED));
49+
lines.add(Component.empty());
50+
lines.add(Component.literal(recipeCategory.getRecipeType().getUid().toString()).withStyle(ChatFormatting.GRAY));
51+
ResourceLocation registryName = recipeCategory.getRegistryName(recipe);
52+
if (registryName != null) {
53+
lines.add(Component.empty());
54+
lines.add(Component.literal(registryName.toString()).withStyle(ChatFormatting.GRAY));
55+
}
56+
57+
IJeiRuntime jeiRuntime = Internal.getJeiRuntime();
58+
IJeiHelpers jeiHelpers = jeiRuntime.getJeiHelpers();
59+
IGuiHelper guiHelper = jeiHelpers.getGuiHelper();
60+
this.scrollBoxWidget = guiHelper.createScrollBoxWidget(area.width(), area.getHeight(), 0, 0)
61+
.setContents(lines);
62+
63+
this.inputHandler = new OffsetJeiInputHandler(this.scrollBoxWidget, this::getScreenPosition);
64+
}
65+
66+
private ScreenPosition getScreenPosition() {
67+
return this.area.getScreenPosition();
68+
}
69+
70+
@Override
71+
public void setPosition(int posX, int posY) {
72+
this.area = this.area.setPosition(posX, posY);
73+
}
74+
75+
@Override
76+
public void drawRecipe(GuiGraphics guiGraphics, int mouseX, int mouseY) {
77+
background.draw(guiGraphics, getRectWithBorder());
78+
79+
PoseStack poseStack = guiGraphics.pose();
80+
poseStack.pushPose();
81+
{
82+
poseStack.translate(area.x(), area.y(), 0);
83+
scrollBoxWidget.draw(guiGraphics, mouseX, mouseY);
84+
}
85+
poseStack.popPose();
86+
}
87+
88+
@Override
89+
public void drawOverlays(GuiGraphics guiGraphics, int mouseX, int mouseY) {
90+
91+
}
92+
93+
@Override
94+
public boolean isMouseOver(double mouseX, double mouseY) {
95+
return area.contains(mouseX, mouseY);
96+
}
97+
98+
@Override
99+
public <T> Optional<T> getIngredientUnderMouse(int mouseX, int mouseY, IIngredientType<T> ingredientType) {
100+
return Optional.empty();
101+
}
102+
103+
@Override
104+
public Optional<IRecipeSlotDrawable> getRecipeSlotUnderMouse(double mouseX, double mouseY) {
105+
return Optional.empty();
106+
}
107+
108+
@Override
109+
public Optional<RecipeSlotUnderMouse> getSlotUnderMouse(double mouseX, double mouseY) {
110+
return Optional.empty();
111+
}
112+
113+
@Override
114+
public Rect2i getRect() {
115+
return area.toMutable();
116+
}
117+
118+
@Override
119+
public Rect2i getRectWithBorder() {
120+
return area.expandBy(borderPadding).toMutable();
121+
}
122+
123+
@Override
124+
public Rect2i getRecipeTransferButtonArea() {
125+
return new Rect2i(0, 0, 0, 0);
126+
}
127+
128+
@Override
129+
public Rect2i getRecipeBookmarkButtonArea() {
130+
return new Rect2i(0, 0, 0, 0);
131+
}
132+
133+
@Override
134+
public IRecipeSlotsView getRecipeSlotsView() {
135+
return List::of;
136+
}
137+
138+
@Override
139+
public IRecipeCategory<R> getRecipeCategory() {
140+
return recipeCategory;
141+
}
142+
143+
@Override
144+
public R getRecipe() {
145+
return recipe;
146+
}
147+
148+
@Override
149+
public IJeiInputHandler getInputHandler() {
150+
return inputHandler;
151+
}
152+
153+
@Override
154+
public void tick() {
155+
156+
}
157+
}

Library/src/main/java/mezz/jei/library/gui/helpers/GuiHelper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ public IScrollBoxWidget createScrollBoxWidget(IDrawable contents, int visibleHei
158158
return widget;
159159
}
160160

161+
@Override
162+
public IScrollBoxWidget createScrollBoxWidget(int width, int height, int xPos, int yPos) {
163+
return new ScrollBoxRecipeWidget(width, height, xPos, yPos);
164+
}
165+
161166
@SuppressWarnings("removal")
162167
@Override
163168
public int getScrollBoxScrollbarExtraWidth() {

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,4 @@ modrinthId=u6dRKJwZ
7474
jUnitVersion=5.8.2
7575

7676
# Version
77-
specificationVersion=19.18.9
77+
specificationVersion=19.18.10

0 commit comments

Comments
 (0)