package net.minecraft.client.gui; import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.platform.Window; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.ReportedException; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font.DisplayMode; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; import net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner; import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.MultiBufferSource.BufferSource; import net.minecraft.client.renderer.item.ItemStackRenderState; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling; import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling.NineSlice.Border; import net.minecraft.core.component.DataComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ARGB; import net.minecraft.util.FormattedCharSequence; import net.minecraft.util.Mth; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; import org.joml.Vector2ic; @Environment(EnvType.CLIENT) public class GuiGraphics { public static final float MAX_GUI_Z = 10000.0F; public static final float MIN_GUI_Z = -10000.0F; private static final int EXTRA_SPACE_AFTER_FIRST_TOOLTIP_LINE = 2; private final Minecraft minecraft; private final PoseStack pose; private final BufferSource bufferSource; private final GuiGraphics.ScissorStack scissorStack = new GuiGraphics.ScissorStack(); private final GuiSpriteManager sprites; private final ItemStackRenderState scratchItemStackRenderState = new ItemStackRenderState(); private GuiGraphics(Minecraft minecraft, PoseStack pose, BufferSource bufferSource) { this.minecraft = minecraft; this.pose = pose; this.bufferSource = bufferSource; this.sprites = minecraft.getGuiSprites(); } public GuiGraphics(Minecraft minecraft, BufferSource bufferSource) { this(minecraft, new PoseStack(), bufferSource); } /** * {@return returns the width of the GUI screen in pixels} */ public int guiWidth() { return this.minecraft.getWindow().getGuiScaledWidth(); } /** * {@return returns the height of the GUI screen in pixels} */ public int guiHeight() { return this.minecraft.getWindow().getGuiScaledHeight(); } /** * {@return returns the PoseStack used for transformations and rendering.} */ public PoseStack pose() { return this.pose; } /** * Flushes the render state, ending the current batch and enabling depth testing. */ public void flush() { this.bufferSource.endBatch(); } /** * Draws a horizontal line from minX to maxX at the specified y-coordinate with the given color. * * @param minX the x-coordinate of the start point. * @param maxX the x-coordinate of the end point. * @param y the y-coordinate of the line. * @param color the color of the line. */ public void hLine(int minX, int maxX, int y, int color) { this.hLine(RenderType.gui(), minX, maxX, y, color); } /** * Draws a horizontal line from minX to maxX at the specified y-coordinate with the given color using the specified render type. * * @param renderType the render type to use. * @param minX the x-coordinate of the start point. * @param maxX the x-coordinate of the end point. * @param y the y-coordinate of the line. * @param color the color of the line. */ public void hLine(RenderType renderType, int minX, int maxX, int y, int color) { if (maxX < minX) { int i = minX; minX = maxX; maxX = i; } this.fill(renderType, minX, y, maxX + 1, y + 1, color); } /** * Draws a vertical line from minY to maxY at the specified x-coordinate with the given color. * * @param x the x-coordinate of the line. * @param minY the y-coordinate of the start point. * @param maxY the y-coordinate of the end point. * @param color the color of the line. */ public void vLine(int x, int minY, int maxY, int color) { this.vLine(RenderType.gui(), x, minY, maxY, color); } /** * Draws a vertical line from minY to maxY at the specified x-coordinate with the given color using the specified render type. * * @param renderType the render type to use. * @param x the x-coordinate of the line. * @param minY the y-coordinate of the start point. * @param maxY the y-coordinate of the end point. * @param color the color of the line. */ public void vLine(RenderType renderType, int x, int minY, int maxY, int color) { if (maxY < minY) { int i = minY; minY = maxY; maxY = i; } this.fill(renderType, x, minY + 1, x + 1, maxY, color); } /** * Enables scissoring with the specified screen coordinates. * * @param minX the minimum x-coordinate of the scissor region. * @param minY the minimum y-coordinate of the scissor region. * @param maxX the maximum x-coordinate of the scissor region. * @param maxY the maximum y-coordinate of the scissor region. */ public void enableScissor(int minX, int minY, int maxX, int maxY) { ScreenRectangle screenRectangle = new ScreenRectangle(minX, minY, maxX - minX, maxY - minY).transformAxisAligned(this.pose.last().pose()); this.applyScissor(this.scissorStack.push(screenRectangle)); } /** * Disables scissoring. */ public void disableScissor() { this.applyScissor(this.scissorStack.pop()); } public boolean containsPointInScissor(int x, int y) { return this.scissorStack.containsPoint(x, y); } /** * Applies scissoring based on the provided screen rectangle. * * @param rectangle the screen rectangle to apply scissoring with. Can be null to disable scissoring. */ private void applyScissor(@Nullable ScreenRectangle rectangle) { this.flush(); if (rectangle != null) { Window window = Minecraft.getInstance().getWindow(); int i = window.getHeight(); double d = window.getGuiScale(); double e = rectangle.left() * d; double f = i - rectangle.bottom() * d; double g = rectangle.width() * d; double h = rectangle.height() * d; RenderSystem.enableScissor((int)e, (int)f, Math.max(0, (int)g), Math.max(0, (int)h)); } else { RenderSystem.disableScissor(); } } /** * Fills a rectangle with the specified color using the given coordinates as the boundaries. * * @param minX the minimum x-coordinate of the rectangle. * @param minY the minimum y-coordinate of the rectangle. * @param maxX the maximum x-coordinate of the rectangle. * @param maxY the maximum y-coordinate of the rectangle. * @param color the color to fill the rectangle with. */ public void fill(int minX, int minY, int maxX, int maxY, int color) { this.fill(minX, minY, maxX, maxY, 0, color); } /** * Fills a rectangle with the specified color and z-level using the given coordinates as the boundaries. * * @param minX the minimum x-coordinate of the rectangle. * @param minY the minimum y-coordinate of the rectangle. * @param maxX the maximum x-coordinate of the rectangle. * @param maxY the maximum y-coordinate of the rectangle. * @param z the z-level of the rectangle. * @param color the color to fill the rectangle with. */ public void fill(int minX, int minY, int maxX, int maxY, int z, int color) { this.fill(RenderType.gui(), minX, minY, maxX, maxY, z, color); } /** * Fills a rectangle with the specified color using the given render type and coordinates as the boundaries. * * @param renderType the render type to use. * @param minX the minimum x-coordinate of the rectangle. * @param minY the minimum y-coordinate of the rectangle. * @param maxX the maximum x-coordinate of the rectangle. * @param maxY the maximum y-coordinate of the rectangle. * @param color the color to fill the rectangle with. */ public void fill(RenderType renderType, int minX, int minY, int maxX, int maxY, int color) { this.fill(renderType, minX, minY, maxX, maxY, 0, color); } /** * Fills a rectangle with the specified color and z-level using the given render type and coordinates as the boundaries. * * @param renderType the render type to use. * @param minX the minimum x-coordinate of the rectangle. * @param minY the minimum y-coordinate of the rectangle. * @param maxX the maximum x-coordinate of the rectangle. * @param maxY the maximum y-coordinate of the rectangle. * @param z the z-level of the rectangle. * @param color the color to fill the rectangle with. */ public void fill(RenderType renderType, int minX, int minY, int maxX, int maxY, int z, int color) { Matrix4f matrix4f = this.pose.last().pose(); if (minX < maxX) { int i = minX; minX = maxX; maxX = i; } if (minY < maxY) { int i = minY; minY = maxY; maxY = i; } VertexConsumer vertexConsumer = this.bufferSource.getBuffer(renderType); vertexConsumer.addVertex(matrix4f, (float)minX, (float)minY, (float)z).setColor(color); vertexConsumer.addVertex(matrix4f, (float)minX, (float)maxY, (float)z).setColor(color); vertexConsumer.addVertex(matrix4f, (float)maxX, (float)maxY, (float)z).setColor(color); vertexConsumer.addVertex(matrix4f, (float)maxX, (float)minY, (float)z).setColor(color); } /** * Fills a rectangle with a gradient color from colorFrom to colorTo using the given coordinates as the boundaries. * * @param x1 the x-coordinate of the first corner of the rectangle. * @param y1 the y-coordinate of the first corner of the rectangle. * @param x2 the x-coordinate of the second corner of the rectangle. * @param y2 the y-coordinate of the second corner of the rectangle. * @param colorFrom the starting color of the gradient. * @param colorTo the ending color of the gradient. */ public void fillGradient(int x1, int y1, int x2, int y2, int colorFrom, int colorTo) { this.fillGradient(x1, y1, x2, y2, 0, colorFrom, colorTo); } /** * Fills a rectangle with a gradient color from colorFrom to colorTo at the specified z-level using the given coordinates as the boundaries. * * @param x1 the x-coordinate of the first corner of the rectangle. * @param y1 the y-coordinate of the first corner of the rectangle. * @param x2 the x-coordinate of the second corner of the rectangle. * @param y2 the y-coordinate of the second corner of the rectangle. * @param z the z-level of the rectangle. * @param colorFrom the starting color of the gradient. * @param colorTo the ending color of the gradient. */ public void fillGradient(int x1, int y1, int x2, int y2, int z, int colorFrom, int colorTo) { this.fillGradient(RenderType.gui(), x1, y1, x2, y2, colorFrom, colorTo, z); } /** * Fills a rectangle with a gradient color from colorFrom to colorTo at the specified z-level using the given render type and coordinates as the boundaries. * * @param renderType the render type to use. * @param x1 the x-coordinate of the first corner of the rectangle. * @param y1 the y-coordinate of the first corner of the rectangle. * @param x2 the x-coordinate of the second corner of the rectangle. * @param y2 the y-coordinate of the second corner of the rectangle. * @param colorFrom the starting color of the gradient. * @param colorTo the ending color of the gradient. * @param z the z-level of the rectangle. */ public void fillGradient(RenderType renderType, int x1, int y1, int x2, int y2, int colorFrom, int colorTo, int z) { VertexConsumer vertexConsumer = this.bufferSource.getBuffer(renderType); this.fillGradient(vertexConsumer, x1, y1, x2, y2, z, colorFrom, colorTo); } /** * The core `fillGradient` method. *

* Fills a rectangle with a gradient color from colorFrom to colorTo at the specified z-level using the given render type and coordinates as the boundaries. * * @param consumer the {@linkplain VertexConsumer} object for drawing the vertices on screen. * @param x1 the x-coordinate of the first corner of the rectangle. * @param y1 the y-coordinate of the first corner of the rectangle. * @param x2 the x-coordinate of the second corner of the rectangle. * @param y2 the y-coordinate of the second corner of the rectangle. * @param z the z-level of the rectangle. * @param colorFrom the starting color of the gradient. * @param colorTo the ending color of the gradient. */ private void fillGradient(VertexConsumer consumer, int x1, int y1, int x2, int y2, int z, int colorFrom, int colorTo) { Matrix4f matrix4f = this.pose.last().pose(); consumer.addVertex(matrix4f, (float)x1, (float)y1, (float)z).setColor(colorFrom); consumer.addVertex(matrix4f, (float)x1, (float)y2, (float)z).setColor(colorTo); consumer.addVertex(matrix4f, (float)x2, (float)y2, (float)z).setColor(colorTo); consumer.addVertex(matrix4f, (float)x2, (float)y1, (float)z).setColor(colorFrom); } public void fillRenderType(RenderType renderType, int x1, int y1, int x2, int y2, int z) { Matrix4f matrix4f = this.pose.last().pose(); VertexConsumer vertexConsumer = this.bufferSource.getBuffer(renderType); vertexConsumer.addVertex(matrix4f, (float)x1, (float)y1, (float)z); vertexConsumer.addVertex(matrix4f, (float)x1, (float)y2, (float)z); vertexConsumer.addVertex(matrix4f, (float)x2, (float)y2, (float)z); vertexConsumer.addVertex(matrix4f, (float)x2, (float)y1, (float)z); } /** * Draws a centered string at the specified coordinates using the given font, text, and color. * * @param font the font to use for rendering. * @param text the text to draw. * @param x the x-coordinate of the center of the string. * @param y the y-coordinate of the string. * @param color the color of the string. */ public void drawCenteredString(Font font, String text, int x, int y, int color) { this.drawString(font, text, x - font.width(text) / 2, y, color); } /** * Draws a centered string at the specified coordinates using the given font, text component, and color. * * @param font the font to use for rendering. * @param text the text component to draw. * @param x the x-coordinate of the center of the string. * @param y the y-coordinate of the string. * @param color the color of the string. */ public void drawCenteredString(Font font, Component text, int x, int y, int color) { FormattedCharSequence formattedCharSequence = text.getVisualOrderText(); this.drawString(font, formattedCharSequence, x - font.width(formattedCharSequence) / 2, y, color); } /** * Draws a centered string at the specified coordinates using the given font, formatted character sequence, and color. * * @param font the font to use for rendering. * @param text the formatted character sequence to draw. * @param x the x-coordinate of the center of the string. * @param y the y-coordinate of the string. * @param color the color of the string. */ public void drawCenteredString(Font font, FormattedCharSequence text, int x, int y, int color) { this.drawString(font, text, x - font.width(text) / 2, y, color); } /** * Draws a string at the specified coordinates using the given font, text, and color. Returns the width of the drawn string. *

* @return the width of the drawn string. * * @param font the font to use for rendering. * @param text the text to draw. * @param x the x-coordinate of the string. * @param y the y-coordinate of the string. * @param color the color of the string. */ public int drawString(Font font, @Nullable String text, int x, int y, int color) { return this.drawString(font, text, x, y, color, true); } /** * Draws a string at the specified coordinates using the given font, text, color, and drop shadow. Returns the width of the drawn string. *

* @return the width of the drawn string. * * @param font the font to use for rendering. * @param text the text to draw. * @param x the x-coordinate of the string. * @param y the y-coordinate of the string. * @param color the color of the string. * @param dropShadow whether to apply a drop shadow to the string. */ public int drawString(Font font, @Nullable String text, int x, int y, int color, boolean dropShadow) { return text == null ? 0 : font.drawInBatch(text, (float)x, (float)y, color, dropShadow, this.pose.last().pose(), this.bufferSource, DisplayMode.NORMAL, 0, 15728880); } /** * Draws a formatted character sequence at the specified coordinates using the given font, text, and color. Returns the width of the drawn string. *

* @return the width of the drawn string. * * @param font the font to use for rendering. * @param text the formatted character sequence to draw. * @param x the x-coordinate of the string. * @param y the y-coordinate of the string. * @param color the color of the string */ public int drawString(Font font, FormattedCharSequence text, int x, int y, int color) { return this.drawString(font, text, x, y, color, true); } /** * Draws a formatted character sequence at the specified coordinates using the given font, text, color, and drop shadow. Returns the width of the drawn string. *

* @return returns the width of the drawn string. * * @param font the font to use for rendering. * @param text the formatted character sequence to draw. * @param x the x-coordinate of the string. * @param y the y-coordinate of the string. * @param color the color of the string. * @param dropShadow whether to apply a drop shadow to the string. */ public int drawString(Font font, FormattedCharSequence text, int x, int y, int color, boolean dropShadow) { return font.drawInBatch(text, (float)x, (float)y, color, dropShadow, this.pose.last().pose(), this.bufferSource, DisplayMode.NORMAL, 0, 15728880); } /** * Draws a component's visual order text at the specified coordinates using the given font, text component, and color. *

* @return the width of the drawn string. * * @param font the font to use for rendering. * @param text the text component to draw. * @param x the x-coordinate of the string. * @param y the y-coordinate of the string. * @param color the color of the string. */ public int drawString(Font font, Component text, int x, int y, int color) { return this.drawString(font, text, x, y, color, true); } /** * Draws a component's visual order text at the specified coordinates using the given font, text component, color, and drop shadow. *

* @return the width of the drawn string. * * @param font the font to use for rendering. * @param text the text component to draw. * @param x the x-coordinate of the string. * @param y the y-coordinate of the string. * @param color the color of the string. * @param dropShadow whether to apply a drop shadow to the string. */ public int drawString(Font font, Component text, int x, int y, int color, boolean dropShadow) { return this.drawString(font, text.getVisualOrderText(), x, y, color, dropShadow); } /** * Draws a formatted text with word wrapping at the specified coordinates using the given font, text, line width, and color. * * @param font the font to use for rendering. * @param text the formatted text to draw. * @param x the x-coordinate of the starting position. * @param y the y-coordinate of the starting position. * @param lineWidth the maximum width of each line before wrapping. * @param color the color of the text. */ public void drawWordWrap(Font font, FormattedText text, int x, int y, int lineWidth, int color) { this.drawWordWrap(font, text, x, y, lineWidth, color, true); } public void drawWordWrap(Font font, FormattedText text, int x, int y, int lineWidth, int color, boolean dropShadow) { for (FormattedCharSequence formattedCharSequence : font.split(text, lineWidth)) { this.drawString(font, formattedCharSequence, x, y, color, dropShadow); y += 9; } } public int drawStringWithBackdrop(Font font, Component text, int x, int y, int xOffset, int color) { int i = this.minecraft.options.getBackgroundColor(0.0F); if (i != 0) { int j = 2; this.fill(x - 2, y - 2, x + xOffset + 2, y + 9 + 2, ARGB.multiply(i, color)); } return this.drawString(font, text, x, y, color, true); } /** * Renders an outline rectangle on the screen with the specified color. * * @param x the x-coordinate of the top-left corner of the rectangle. * @param y the y-coordinate of the top-left corner of the rectangle. * @param width the width of the blitted portion. * @param height the height of the rectangle. * @param color the color of the outline. */ public void renderOutline(int x, int y, int width, int height, int color) { this.fill(x, y, x + width, y + 1, color); this.fill(x, y + height - 1, x + width, y + height, color); this.fill(x, y + 1, x + 1, y + height - 1, color); this.fill(x + width - 1, y + 1, x + width, y + height - 1, color); } public void blitSprite(Function renderTypeGetter, ResourceLocation sprite, int x, int y, int width, int height) { this.blitSprite(renderTypeGetter, sprite, x, y, width, height, -1); } public void blitSprite(Function renderTypeGetter, ResourceLocation sprite, int x, int y, int width, int height, int blitOffset) { TextureAtlasSprite textureAtlasSprite = this.sprites.getSprite(sprite); GuiSpriteScaling guiSpriteScaling = this.sprites.getSpriteScaling(textureAtlasSprite); if (guiSpriteScaling instanceof GuiSpriteScaling.Stretch) { this.blitSprite(renderTypeGetter, textureAtlasSprite, x, y, width, height, blitOffset); } else if (guiSpriteScaling instanceof GuiSpriteScaling.Tile tile) { this.blitTiledSprite(renderTypeGetter, textureAtlasSprite, x, y, width, height, 0, 0, tile.width(), tile.height(), tile.width(), tile.height(), blitOffset); } else if (guiSpriteScaling instanceof GuiSpriteScaling.NineSlice nineSlice) { this.blitNineSlicedSprite(renderTypeGetter, textureAtlasSprite, nineSlice, x, y, width, height, blitOffset); } } public void blitSprite( Function renderTypeGetter, ResourceLocation sprite, int textureWidth, int textureHeight, int uPosition, int vPosition, int x, int y, int uWidth, int vHeight ) { TextureAtlasSprite textureAtlasSprite = this.sprites.getSprite(sprite); GuiSpriteScaling guiSpriteScaling = this.sprites.getSpriteScaling(textureAtlasSprite); if (guiSpriteScaling instanceof GuiSpriteScaling.Stretch) { this.blitSprite(renderTypeGetter, textureAtlasSprite, textureWidth, textureHeight, uPosition, vPosition, x, y, uWidth, vHeight, -1); } else { this.enableScissor(x, y, x + uWidth, y + vHeight); this.blitSprite(renderTypeGetter, sprite, x - uPosition, y - vPosition, textureWidth, textureHeight, -1); this.disableScissor(); } } public void blitSprite(Function renderTypeGetter, TextureAtlasSprite sprite, int x, int y, int width, int height) { this.blitSprite(renderTypeGetter, sprite, x, y, width, height, -1); } public void blitSprite(Function renderTypeGetter, TextureAtlasSprite sprite, int x, int y, int width, int height, int blitOffset) { if (width != 0 && height != 0) { this.innerBlit( renderTypeGetter, sprite.atlasLocation(), x, x + width, y, y + height, sprite.getU0(), sprite.getU1(), sprite.getV0(), sprite.getV1(), blitOffset ); } } private void blitSprite( Function renderTypeGetter, TextureAtlasSprite sprite, int textureWidth, int textureHeight, int uPosition, int vPosition, int x, int y, int uWidth, int vHeight, int blitOffset ) { if (uWidth != 0 && vHeight != 0) { this.innerBlit( renderTypeGetter, sprite.atlasLocation(), x, x + uWidth, y, y + vHeight, sprite.getU((float)uPosition / textureWidth), sprite.getU((float)(uPosition + uWidth) / textureWidth), sprite.getV((float)vPosition / textureHeight), sprite.getV((float)(vPosition + vHeight) / textureHeight), blitOffset ); } } private void blitNineSlicedSprite( Function renderTypeGetter, TextureAtlasSprite sprite, GuiSpriteScaling.NineSlice nineSlice, int x, int y, int blitOffset, int width, int height ) { Border border = nineSlice.border(); int i = Math.min(border.left(), blitOffset / 2); int j = Math.min(border.right(), blitOffset / 2); int k = Math.min(border.top(), width / 2); int l = Math.min(border.bottom(), width / 2); if (blitOffset == nineSlice.width() && width == nineSlice.height()) { this.blitSprite(renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), 0, 0, x, y, blitOffset, width, height); } else if (width == nineSlice.height()) { this.blitSprite(renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), 0, 0, x, y, i, width, height); this.blitNineSliceInnerSegment( renderTypeGetter, nineSlice, sprite, x + i, y, blitOffset - j - i, width, i, 0, nineSlice.width() - j - i, nineSlice.height(), nineSlice.width(), nineSlice.height(), height ); this.blitSprite(renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), nineSlice.width() - j, 0, x + blitOffset - j, y, j, width, height); } else if (blitOffset == nineSlice.width()) { this.blitSprite(renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), 0, 0, x, y, blitOffset, k, height); this.blitNineSliceInnerSegment( renderTypeGetter, nineSlice, sprite, x, y + k, blitOffset, width - l - k, 0, k, nineSlice.width(), nineSlice.height() - l - k, nineSlice.width(), nineSlice.height(), height ); this.blitSprite(renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), 0, nineSlice.height() - l, x, y + width - l, blitOffset, l, height); } else { this.blitSprite(renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), 0, 0, x, y, i, k, height); this.blitNineSliceInnerSegment( renderTypeGetter, nineSlice, sprite, x + i, y, blitOffset - j - i, k, i, 0, nineSlice.width() - j - i, k, nineSlice.width(), nineSlice.height(), height ); this.blitSprite(renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), nineSlice.width() - j, 0, x + blitOffset - j, y, j, k, height); this.blitSprite(renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), 0, nineSlice.height() - l, x, y + width - l, i, l, height); this.blitNineSliceInnerSegment( renderTypeGetter, nineSlice, sprite, x + i, y + width - l, blitOffset - j - i, l, i, nineSlice.height() - l, nineSlice.width() - j - i, l, nineSlice.width(), nineSlice.height(), height ); this.blitSprite( renderTypeGetter, sprite, nineSlice.width(), nineSlice.height(), nineSlice.width() - j, nineSlice.height() - l, x + blitOffset - j, y + width - l, j, l, height ); this.blitNineSliceInnerSegment( renderTypeGetter, nineSlice, sprite, x, y + k, i, width - l - k, 0, k, i, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height(), height ); this.blitNineSliceInnerSegment( renderTypeGetter, nineSlice, sprite, x + i, y + k, blitOffset - j - i, width - l - k, i, k, nineSlice.width() - j - i, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height(), height ); this.blitNineSliceInnerSegment( renderTypeGetter, nineSlice, sprite, x + blitOffset - j, y + k, j, width - l - k, nineSlice.width() - j, k, j, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height(), height ); } } private void blitNineSliceInnerSegment( Function renderTypeGetter, GuiSpriteScaling.NineSlice nineSlice, TextureAtlasSprite sprite, int x, int y, int width, int height, int uPosition, int vPosition, int spriteWidth, int spriteHeight, int nineSliceWidth, int nineSliceHeight, int blitOffset ) { if (width > 0 && height > 0) { if (nineSlice.stretchInner()) { this.innerBlit( renderTypeGetter, sprite.atlasLocation(), x, x + width, y, y + height, sprite.getU((float)uPosition / nineSliceWidth), sprite.getU((float)(uPosition + spriteWidth) / nineSliceWidth), sprite.getV((float)vPosition / nineSliceHeight), sprite.getV((float)(vPosition + spriteHeight) / nineSliceHeight), blitOffset ); } else { this.blitTiledSprite( renderTypeGetter, sprite, x, y, width, height, uPosition, vPosition, spriteWidth, spriteHeight, nineSliceWidth, nineSliceHeight, blitOffset ); } } } private void blitTiledSprite( Function renderTypeGetter, TextureAtlasSprite sprite, int x, int y, int width, int height, int uPosition, int vPosition, int spriteWidth, int spriteHeight, int nineSliceWidth, int nineSliceHeight, int blitOffset ) { if (width > 0 && height > 0) { if (spriteWidth > 0 && spriteHeight > 0) { for (int i = 0; i < width; i += spriteWidth) { int j = Math.min(spriteWidth, width - i); for (int k = 0; k < height; k += spriteHeight) { int l = Math.min(spriteHeight, height - k); this.blitSprite(renderTypeGetter, sprite, nineSliceWidth, nineSliceHeight, uPosition, vPosition, x + i, y + k, j, l, blitOffset); } } } else { throw new IllegalArgumentException("Tiled sprite texture size must be positive, got " + spriteWidth + "x" + spriteHeight); } } } public void blit( Function renderTypeGetter, ResourceLocation atlasLocation, int x, int y, float uOffset, float vOffset, int uWidth, int vHeight, int textureWidth, int textureHeight, int color ) { this.blit(renderTypeGetter, atlasLocation, x, y, uOffset, vOffset, uWidth, vHeight, uWidth, vHeight, textureWidth, textureHeight, color); } public void blit( Function renderTypeGetter, ResourceLocation atlasLocation, int x, int y, float uOffset, float vOffset, int uWidth, int vHeight, int textureWidth, int textureHeight ) { this.blit(renderTypeGetter, atlasLocation, x, y, uOffset, vOffset, uWidth, vHeight, uWidth, vHeight, textureWidth, textureHeight); } public void blit( Function renderTypeGetter, ResourceLocation atlasLocation, int x, int y, float uOffset, float vOffset, int uWidth, int vHeight, int width, int height, int textureWidth, int textureHeight ) { this.blit(renderTypeGetter, atlasLocation, x, y, uOffset, vOffset, uWidth, vHeight, width, height, textureWidth, textureHeight, -1); } public void blit( Function renderTypeGetter, ResourceLocation atlasLocation, int x, int y, float uOffset, float vOffset, int uWidth, int vHeight, int width, int height, int textureWidth, int textureHeight, int color ) { this.innerBlit( renderTypeGetter, atlasLocation, x, x + uWidth, y, y + vHeight, (uOffset + 0.0F) / textureWidth, (uOffset + width) / textureWidth, (vOffset + 0.0F) / textureHeight, (vOffset + height) / textureHeight, color ); } private void innerBlit( Function renderTypeGetter, ResourceLocation atlasLocation, int x1, int x2, int y1, int y2, float minU, float maxU, float minV, float maxV, int color ) { RenderType renderType = (RenderType)renderTypeGetter.apply(atlasLocation); Matrix4f matrix4f = this.pose.last().pose(); VertexConsumer vertexConsumer = this.bufferSource.getBuffer(renderType); vertexConsumer.addVertex(matrix4f, (float)x1, (float)y1, 0.0F).setUv(minU, minV).setColor(color); vertexConsumer.addVertex(matrix4f, (float)x1, (float)y2, 0.0F).setUv(minU, maxV).setColor(color); vertexConsumer.addVertex(matrix4f, (float)x2, (float)y2, 0.0F).setUv(maxU, maxV).setColor(color); vertexConsumer.addVertex(matrix4f, (float)x2, (float)y1, 0.0F).setUv(maxU, minV).setColor(color); } /** * Renders an item stack at the specified coordinates. * * @param stack the item stack to render. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. */ public void renderItem(ItemStack stack, int x, int y) { this.renderItem(this.minecraft.player, this.minecraft.level, stack, x, y, 0); } /** * Renders an item stack at the specified coordinates with a random seed. * * @param stack the item stack to render. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. * @param seed the random seed. */ public void renderItem(ItemStack stack, int x, int y, int seed) { this.renderItem(this.minecraft.player, this.minecraft.level, stack, x, y, seed); } /** * Renders an item stack at the specified coordinates with a random seed and a custom value. * * @param stack the item stack to render. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. * @param seed the random seed. * @param guiOffset the GUI offset. */ public void renderItem(ItemStack stack, int x, int y, int seed, int guiOffset) { this.renderItem(this.minecraft.player, this.minecraft.level, stack, x, y, seed, guiOffset); } /** * Renders a fake item stack at the specified coordinates. * * @param stack the fake item stack to render. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. */ public void renderFakeItem(ItemStack stack, int x, int y) { this.renderFakeItem(stack, x, y, 0); } public void renderFakeItem(ItemStack stack, int x, int y, int seed) { this.renderItem(null, this.minecraft.level, stack, x, y, seed); } /** * Renders an item stack for a living entity at the specified coordinates with a random seed. * * @param entity the living entity. * @param stack the item stack to render. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. * @param seed the random seed. */ public void renderItem(LivingEntity entity, ItemStack stack, int x, int y, int seed) { this.renderItem(entity, entity.level(), stack, x, y, seed); } /** * Renders an item stack for a living entity in a specific level at the specified coordinates with a random seed. * * @param entity the living entity. Can be null. * @param level the level in which the rendering occurs. Can be null. * @param stack the item stack to render. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. * @param seed the random seed. */ private void renderItem(@Nullable LivingEntity entity, @Nullable Level level, ItemStack stack, int x, int y, int seed) { this.renderItem(entity, level, stack, x, y, seed, 0); } /** * Renders an item stack for a living entity in a specific level at the specified coordinates with a random seed and a custom GUI offset. * * @param entity the living entity. Can be null. * @param level the level in which the rendering occurs. Can be null. * @param stack the item stack to render. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. * @param seed the random seed. * @param guiOffset the GUI offset value. */ private void renderItem(@Nullable LivingEntity entity, @Nullable Level level, ItemStack stack, int x, int y, int seed, int guiOffset) { if (!stack.isEmpty()) { this.minecraft.getItemModelResolver().updateForTopItem(this.scratchItemStackRenderState, stack, ItemDisplayContext.GUI, level, entity, seed); this.pose.pushPose(); this.pose.translate((float)(x + 8), (float)(y + 8), (float)(150 + guiOffset)); try { this.pose.scale(16.0F, -16.0F, 16.0F); boolean bl = !this.scratchItemStackRenderState.usesBlockLight(); if (bl) { this.flush(); Lighting.setupForFlatItems(); } this.scratchItemStackRenderState.render(this.pose, this.bufferSource, 15728880, OverlayTexture.NO_OVERLAY); this.flush(); if (bl) { Lighting.setupFor3DItems(); } } catch (Throwable var11) { CrashReport crashReport = CrashReport.forThrowable(var11, "Rendering item"); CrashReportCategory crashReportCategory = crashReport.addCategory("Item being rendered"); crashReportCategory.setDetail("Item Type", (CrashReportDetail)(() -> String.valueOf(stack.getItem()))); crashReportCategory.setDetail("Item Components", (CrashReportDetail)(() -> String.valueOf(stack.getComponents()))); crashReportCategory.setDetail("Item Foil", (CrashReportDetail)(() -> String.valueOf(stack.hasFoil()))); throw new ReportedException(crashReport); } this.pose.popPose(); } } /** * Renders additional decorations for an item stack at the specified coordinates. * * @param font the font used for rendering text. * @param stack the item stack to decorate. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. */ public void renderItemDecorations(Font font, ItemStack stack, int x, int y) { this.renderItemDecorations(font, stack, x, y, null); } /** * Renders additional decorations for an item stack at the specified coordinates with optional custom text. * * @param font the font used for rendering text. * @param stack the item stack to decorate. * @param x the x-coordinate of the rendering position. * @param y the y-coordinate of the rendering position. * @param text the custom text to display. Can be null. */ public void renderItemDecorations(Font font, ItemStack stack, int x, int y, @Nullable String text) { if (!stack.isEmpty()) { this.pose.pushPose(); this.renderItemBar(stack, x, y); this.renderItemCount(font, stack, x, y, text); this.renderItemCooldown(stack, x, y); this.pose.popPose(); } } /** * Renders a tooltip for an item stack at the specified mouse coordinates. * * @param font the font used for rendering text. * @param stack the item stack to display the tooltip for. * @param mouseX the x-coordinate of the mouse position. * @param mouseY the y-coordinate of the mouse position. */ public void renderTooltip(Font font, ItemStack stack, int mouseX, int mouseY) { this.renderTooltip(font, Screen.getTooltipFromItem(this.minecraft, stack), stack.getTooltipImage(), mouseX, mouseY, stack.get(DataComponents.TOOLTIP_STYLE)); } /** * Renders a tooltip with customizable components at the specified mouse coordinates. * * @param font the font used for rendering text. * @param tooltipLines the lines of the tooltip. * @param visualTooltipComponent the visual tooltip component. Can be empty. * @param mouseX the x-coordinate of the mouse position. * @param mouseY the y-coordinate of the mouse position. */ public void renderTooltip(Font font, List tooltipLines, Optional visualTooltipComponent, int mouseX, int mouseY) { this.renderTooltip(font, tooltipLines, visualTooltipComponent, mouseX, mouseY, null); } public void renderTooltip( Font font, List tooltipLines, Optional visualTooltipComponent, int mouseX, int mouseY, @Nullable ResourceLocation sprite ) { List list = (List)tooltipLines.stream() .map(Component::getVisualOrderText) .map(ClientTooltipComponent::create) .collect(Util.toMutableList()); visualTooltipComponent.ifPresent(tooltipComponent -> list.add(list.isEmpty() ? 0 : 1, ClientTooltipComponent.create(tooltipComponent))); this.renderTooltipInternal(font, list, mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, sprite); } /** * Renders a tooltip with a single line of text at the specified mouse coordinates. * * @param font the font used for rendering text. * @param text the text to display in the tooltip. * @param mouseX the x-coordinate of the mouse position. * @param mouseY the y-coordinate of the mouse position. */ public void renderTooltip(Font font, Component text, int mouseX, int mouseY) { this.renderTooltip(font, text, mouseX, mouseY, null); } public void renderTooltip(Font font, Component text, int mouseX, int mouseY, @Nullable ResourceLocation sprite) { this.renderTooltip(font, List.of(text.getVisualOrderText()), mouseX, mouseY, sprite); } /** * Renders a tooltip with multiple lines of component-based text at the specified mouse coordinates. * * @param font the font used for rendering text. * @param tooltipLines the lines of the tooltip as components. * @param mouseX the x-coordinate of the mouse position. * @param mouseY the y-coordinate of the mouse position. */ public void renderComponentTooltip(Font font, List tooltipLines, int mouseX, int mouseY) { this.renderComponentTooltip(font, tooltipLines, mouseX, mouseY, null); } public void renderComponentTooltip(Font font, List tooltipLines, int mouseX, int mouseY, @Nullable ResourceLocation sprite) { this.renderTooltipInternal( font, tooltipLines.stream().map(Component::getVisualOrderText).map(ClientTooltipComponent::create).toList(), mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, sprite ); } /** * Renders a tooltip with multiple lines of formatted text at the specified mouse coordinates. * * @param font the font used for rendering text. * @param tooltipLines the lines of the tooltip as formatted character sequences. * @param mouseX the x-coordinate of the mouse position. * @param mouseY the y-coordinate of the mouse position. */ public void renderTooltip(Font font, List tooltipLines, int mouseX, int mouseY) { this.renderTooltip(font, tooltipLines, mouseX, mouseY, null); } public void renderTooltip(Font font, List tooltipLines, int mouseX, int mouseY, @Nullable ResourceLocation sprite) { this.renderTooltipInternal( font, (List)tooltipLines.stream().map(ClientTooltipComponent::create).collect(Collectors.toList()), mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, sprite ); } /** * Renders a tooltip with multiple lines of formatted text using a custom tooltip positioner at the specified mouse coordinates. * * @param font the font used for rendering text. * @param tooltipLines the lines of the tooltip as formatted character sequences. * @param tooltipPositioner the positioner to determine the tooltip's position. * @param mouseX the x-coordinate of the mouse position. * @param mouseY the y-coordinate of the mouse position. */ public void renderTooltip(Font font, List tooltipLines, ClientTooltipPositioner tooltipPositioner, int mouseX, int mouseY) { this.renderTooltipInternal( font, (List)tooltipLines.stream().map(ClientTooltipComponent::create).collect(Collectors.toList()), mouseX, mouseY, tooltipPositioner, null ); } private void renderTooltipInternal( Font font, List tooltipLines, int mouseX, int mouseY, ClientTooltipPositioner tooltipPositioner, @Nullable ResourceLocation sprite ) { if (!tooltipLines.isEmpty()) { int i = 0; int j = tooltipLines.size() == 1 ? -2 : 0; for (ClientTooltipComponent clientTooltipComponent : tooltipLines) { int k = clientTooltipComponent.getWidth(font); if (k > i) { i = k; } j += clientTooltipComponent.getHeight(font); } int l = i; int m = j; Vector2ic vector2ic = tooltipPositioner.positionTooltip(this.guiWidth(), this.guiHeight(), mouseX, mouseY, i, j); int n = vector2ic.x(); int o = vector2ic.y(); this.pose.pushPose(); int p = 400; TooltipRenderUtil.renderTooltipBackground(this, n, o, i, j, 400, sprite); this.pose.translate(0.0F, 0.0F, 400.0F); int q = o; for (int r = 0; r < tooltipLines.size(); r++) { ClientTooltipComponent clientTooltipComponent2 = (ClientTooltipComponent)tooltipLines.get(r); clientTooltipComponent2.renderText(font, n, q, this.pose.last().pose(), this.bufferSource); q += clientTooltipComponent2.getHeight(font) + (r == 0 ? 2 : 0); } q = o; for (int r = 0; r < tooltipLines.size(); r++) { ClientTooltipComponent clientTooltipComponent2 = (ClientTooltipComponent)tooltipLines.get(r); clientTooltipComponent2.renderImage(font, n, q, l, m, this); q += clientTooltipComponent2.getHeight(font) + (r == 0 ? 2 : 0); } this.pose.popPose(); } } private void renderItemBar(ItemStack stack, int x, int y) { if (stack.isBarVisible()) { int i = x + 2; int j = y + 13; this.fill(RenderType.gui(), i, j, i + 13, j + 2, 200, -16777216); this.fill(RenderType.gui(), i, j, i + stack.getBarWidth(), j + 1, 200, ARGB.opaque(stack.getBarColor())); } } private void renderItemCount(Font font, ItemStack stack, int x, int y, @Nullable String text) { if (stack.getCount() != 1 || text != null) { String string = text == null ? String.valueOf(stack.getCount()) : text; this.pose.pushPose(); this.pose.translate(0.0F, 0.0F, 200.0F); this.drawString(font, string, x + 19 - 2 - font.width(string), y + 6 + 3, -1, true); this.pose.popPose(); } } private void renderItemCooldown(ItemStack stack, int x, int y) { LocalPlayer localPlayer = this.minecraft.player; float f = localPlayer == null ? 0.0F : localPlayer.getCooldowns().getCooldownPercent(stack, this.minecraft.getDeltaTracker().getGameTimeDeltaPartialTick(true)); if (f > 0.0F) { int i = y + Mth.floor(16.0F * (1.0F - f)); int j = i + Mth.ceil(16.0F * f); this.fill(RenderType.gui(), x, i, x + 16, j, 200, Integer.MAX_VALUE); } } /** * Renders a hover effect for a text component at the specified mouse coordinates. * * @param font the font used for rendering text. * @param style the style of the text component. Can be null. * @param mouseX the x-coordinate of the mouse position. * @param mouseY the y-coordinate of the mouse position. */ public void renderComponentHoverEffect(Font font, @Nullable Style style, int mouseX, int mouseY) { if (style != null && style.getHoverEvent() != null) { switch (style.getHoverEvent()) { case HoverEvent.ShowItem(ItemStack var17): this.renderTooltip(font, var17, mouseX, mouseY); break; case HoverEvent.ShowEntity(HoverEvent.EntityTooltipInfo var22): HoverEvent.EntityTooltipInfo var18 = var22; if (this.minecraft.options.advancedItemTooltips) { this.renderComponentTooltip(font, var18.getTooltipLines(), mouseX, mouseY); } break; case HoverEvent.ShowText(Component var13): this.renderTooltip(font, font.split(var13, Math.max(this.guiWidth() / 2, 200)), mouseX, mouseY); break; default: } } } public void drawSpecial(Consumer drawer) { drawer.accept(this.bufferSource); this.bufferSource.endBatch(); } /** * A utility class for managing a stack of screen rectangles for scissoring. */ @Environment(EnvType.CLIENT) static class ScissorStack { private final Deque stack = new ArrayDeque(); /** * Pushes a screen rectangle onto the scissor stack. *

* @return The resulting intersection of the pushed rectangle with the previous top rectangle on the stack, or the pushed rectangle if the stack is empty. * * @param scissor the screen rectangle to push. */ public ScreenRectangle push(ScreenRectangle scissor) { ScreenRectangle screenRectangle = (ScreenRectangle)this.stack.peekLast(); if (screenRectangle != null) { ScreenRectangle screenRectangle2 = (ScreenRectangle)Objects.requireNonNullElse(scissor.intersection(screenRectangle), ScreenRectangle.empty()); this.stack.addLast(screenRectangle2); return screenRectangle2; } else { this.stack.addLast(scissor); return scissor; } } /** * Pops the top screen rectangle from the scissor stack. *

* @return The new top screen rectangle after the pop operation, or null if the stack is empty. * @throws IllegalStateException if the stack is empty. */ @Nullable public ScreenRectangle pop() { if (this.stack.isEmpty()) { throw new IllegalStateException("Scissor stack underflow"); } else { this.stack.removeLast(); return (ScreenRectangle)this.stack.peekLast(); } } public boolean containsPoint(int x, int y) { return this.stack.isEmpty() ? true : ((ScreenRectangle)this.stack.peek()).containsPoint(x, y); } } }