-
-
Notifications
You must be signed in to change notification settings - Fork 509
/
GuiUtils.java
332 lines (302 loc) · 16.4 KB
/
GuiUtils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
package mekanism.client.gui;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.VertexFormat.Mode;
import java.util.List;
import java.util.function.Predicate;
import mekanism.client.gui.element.GuiElement;
import mekanism.common.Mekanism;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FastColor;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
public class GuiUtils {
private GuiUtils() {
}
// Note: Does not validate that the passed in dimensions are valid
// this strategy starts with a small texture and will expand it (by scaling) to meet the size requirements. good for small widgets
// where the background texture is a single color
//TODO - 1.20: Compare against GuiGraphics#blitNineSliced
// Maybe something along the lines of guiGraphics.blitNineSliced(resource, left, top, width, height, , , , , );
public static void renderExtendedTexture(GuiGraphics guiGraphics, ResourceLocation resource, int sideWidth, int sideHeight, int left, int top, int width, int height) {
int textureWidth = 2 * sideWidth + 1;
int textureHeight = 2 * sideHeight + 1;
int centerWidth = width - 2 * sideWidth;
int centerHeight = height - 2 * sideHeight;
int leftEdgeEnd = left + sideWidth;
int rightEdgeStart = leftEdgeEnd + centerWidth;
int topEdgeEnd = top + sideHeight;
int bottomEdgeStart = topEdgeEnd + centerHeight;
//Left Side
//Top Left Corner
guiGraphics.blit(resource, left, top, 0, 0, sideWidth, sideHeight, textureWidth, textureHeight);
//Left Middle
if (centerHeight > 0) {
guiGraphics.blit(resource, left, topEdgeEnd, sideWidth, centerHeight, 0, sideHeight, sideWidth, 1, textureWidth, textureHeight);
}
//Bottom Left Corner
guiGraphics.blit(resource, left, bottomEdgeStart, 0, sideHeight + 1, sideWidth, sideHeight, textureWidth, textureHeight);
//Middle
if (centerWidth > 0) {
//Top Middle
guiGraphics.blit(resource, leftEdgeEnd, top, centerWidth, sideHeight, sideWidth, 0, 1, sideHeight, textureWidth, textureHeight);
if (centerHeight > 0) {
//Center
guiGraphics.blit(resource, leftEdgeEnd, topEdgeEnd, centerWidth, centerHeight, sideWidth, sideHeight, 1, 1, textureWidth, textureHeight);
}
//Bottom Middle
guiGraphics.blit(resource, leftEdgeEnd, bottomEdgeStart, centerWidth, sideHeight, sideWidth, sideHeight + 1, 1, sideHeight, textureWidth, textureHeight);
}
//Right side
//Top Right Corner
guiGraphics.blit(resource, rightEdgeStart, top, sideWidth + 1, 0, sideWidth, sideHeight, textureWidth, textureHeight);
//Right Middle
if (centerHeight > 0) {
guiGraphics.blit(resource, rightEdgeStart, topEdgeEnd, sideWidth, centerHeight, sideWidth + 1, sideHeight, sideWidth, 1, textureWidth, textureHeight);
}
//Bottom Right Corner
guiGraphics.blit(resource, rightEdgeStart, bottomEdgeStart, sideWidth + 1, sideHeight + 1, sideWidth, sideHeight, textureWidth, textureHeight);
}
// this strategy starts with a large texture and will scale it down or tile it if necessary. good for larger widgets, but requires a large texture; small textures will tank FPS due
// to tiling
public static void renderBackgroundTexture(GuiGraphics guiGraphics, ResourceLocation resource, int texSideWidth, int texSideHeight, int left, int top, int width, int height, int textureWidth, int textureHeight) {
// render as much side as we can, based on element dimensions
int sideWidth = Math.min(texSideWidth, width / 2);
int sideHeight = Math.min(texSideHeight, height / 2);
// Adjustment for small odd-height and odd-width GUIs
int leftWidth = sideWidth < texSideWidth ? sideWidth + (width % 2) : sideWidth;
int topHeight = sideHeight < texSideHeight ? sideHeight + (height % 2) : sideHeight;
int texCenterWidth = textureWidth - texSideWidth * 2, texCenterHeight = textureHeight - texSideHeight * 2;
int centerWidth = width - leftWidth - sideWidth, centerHeight = height - topHeight - sideHeight;
int leftEdgeEnd = left + leftWidth;
int rightEdgeStart = leftEdgeEnd + centerWidth;
int topEdgeEnd = top + topHeight;
int bottomEdgeStart = topEdgeEnd + centerHeight;
//Top Left Corner
guiGraphics.blit(resource, left, top, 0, 0, leftWidth, topHeight, textureWidth, textureHeight);
//Bottom Left Corner
guiGraphics.blit(resource, left, bottomEdgeStart, 0, textureHeight - sideHeight, leftWidth, sideHeight, textureWidth, textureHeight);
//Middle
if (centerWidth > 0) {
//Top Middle
blitTiled(guiGraphics, resource, leftEdgeEnd, top, centerWidth, topHeight, texSideWidth, 0, texCenterWidth, texSideHeight, textureWidth, textureHeight);
if (centerHeight > 0) {
//Center
blitTiled(guiGraphics, resource, leftEdgeEnd, topEdgeEnd, centerWidth, centerHeight, texSideWidth, texSideHeight, texCenterWidth, texCenterHeight, textureWidth, textureHeight);
}
//Bottom Middle
blitTiled(guiGraphics, resource, leftEdgeEnd, bottomEdgeStart, centerWidth, sideHeight, texSideWidth, textureHeight - sideHeight, texCenterWidth, texSideHeight, textureWidth, textureHeight);
}
if (centerHeight > 0) {
//Left Middle
blitTiled(guiGraphics, resource, left, topEdgeEnd, leftWidth, centerHeight, 0, texSideHeight, texSideWidth, texCenterHeight, textureWidth, textureHeight);
//Right Middle
blitTiled(guiGraphics, resource, rightEdgeStart, topEdgeEnd, sideWidth, centerHeight, textureWidth - sideWidth, texSideHeight, texSideWidth, texCenterHeight, textureWidth, textureHeight);
}
//Top Right Corner
guiGraphics.blit(resource, rightEdgeStart, top, textureWidth - sideWidth, 0, sideWidth, topHeight, textureWidth, textureHeight);
//Bottom Right Corner
guiGraphics.blit(resource, rightEdgeStart, bottomEdgeStart, textureWidth - sideWidth, textureHeight - sideHeight, sideWidth, sideHeight, textureWidth, textureHeight);
}
public static void blitTiled(GuiGraphics guiGraphics, ResourceLocation resource, int x, int y, int width, int height, int texX, int texY, int texDrawWidth,
int texDrawHeight, int textureWidth, int textureHeight) {
int xTiles = (int) Math.ceil((float) width / texDrawWidth), yTiles = (int) Math.ceil((float) height / texDrawHeight);
int drawWidth = width, drawHeight = height;
for (int tileX = 0; tileX < xTiles; tileX++) {
for (int tileY = 0; tileY < yTiles; tileY++) {
guiGraphics.blit(resource, x + texDrawWidth * tileX, y + texDrawHeight * tileY, texX, texY, Math.min(drawWidth, texDrawWidth),
Math.min(drawHeight, texDrawHeight), textureWidth, textureHeight);
drawHeight -= texDrawHeight;
}
drawWidth -= texDrawWidth;
drawHeight = height;
}
}
public static void drawOutline(GuiGraphics guiGraphics, int x, int y, int width, int height, int color) {
fill(guiGraphics, x, y, width, 1, color);
fill(guiGraphics, x, y + height - 1, width, 1, color);
if (height > 2) {
fill(guiGraphics, x, y + 1, 1, height - 2, color);
fill(guiGraphics, x + width - 1, y + 1, 1, height - 2, color);
}
}
public static void fill(GuiGraphics guiGraphics, RenderType renderType, int x, int y, int width, int height, int color) {
if (width != 0 && height != 0) {
guiGraphics.fill(renderType, x, y, x + width, y + height, color);
}
}
public static void fill(GuiGraphics guiGraphics, int x, int y, int width, int height, int color) {
if (width != 0 && height != 0) {
guiGraphics.fill(x, y, x + width, y + height, color);
}
}
public static void drawBackdrop(GuiGraphics guiGraphics, Minecraft minecraft, int x, int y, int width, int alpha) {
drawBackdrop(guiGraphics, minecraft, x, y, width, 9, alpha);
}
public static void drawBackdrop(GuiGraphics guiGraphics, Minecraft minecraft, int x, int y, int width, int height, int alpha) {
//Slightly modified copy of Gui#drawBackdrop so that we can support it in places that can't directly call it
int backgroundColor = minecraft.options.getBackgroundColor(0.0F);
if (backgroundColor != 0) {
int argb = 0xFFFFFF | alpha << 24;
guiGraphics.fill(x - 2, y - 2, x + width + 2, y + height + 2, FastColor.ARGB32.multiply(backgroundColor, argb));
}
}
public static void drawTiledSprite(GuiGraphics guiGraphics, int xPosition, int yPosition, int yOffset, int desiredWidth, int desiredHeight, TextureAtlasSprite sprite,
int textureWidth, int textureHeight, int zLevel, TilingDirection tilingDirection) {
drawTiledSprite(guiGraphics, xPosition, yPosition, yOffset, desiredWidth, desiredHeight, sprite, textureWidth, textureHeight, zLevel, tilingDirection, true);
}
public static void drawTiledSprite(GuiGraphics guiGraphics, int xPosition, int yPosition, int yOffset, int desiredWidth, int desiredHeight, TextureAtlasSprite sprite,
int textureWidth, int textureHeight, int zLevel, TilingDirection tilingDirection, boolean blend) {
if (desiredWidth == 0 || desiredHeight == 0 || textureWidth == 0 || textureHeight == 0) {
return;
}
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, sprite.atlasLocation());
int xTileCount = desiredWidth / textureWidth;
int xRemainder = desiredWidth - (xTileCount * textureWidth);
int yTileCount = desiredHeight / textureHeight;
int yRemainder = desiredHeight - (yTileCount * textureHeight);
int yStart = yPosition + yOffset;
float uMin = sprite.getU0();
float uMax = sprite.getU1();
float vMin = sprite.getV0();
float vMax = sprite.getV1();
float uDif = uMax - uMin;
float vDif = vMax - vMin;
if (blend) {
RenderSystem.enableBlend();
}
BufferBuilder vertexBuffer = Tesselator.getInstance().getBuilder();
vertexBuffer.begin(Mode.QUADS, DefaultVertexFormat.POSITION_TEX);
Matrix4f matrix4f = guiGraphics.pose().last().pose();
for (int xTile = 0; xTile <= xTileCount; xTile++) {
int width = (xTile == xTileCount) ? xRemainder : textureWidth;
if (width == 0) {
break;
}
int x = xPosition + (xTile * textureWidth);
int maskRight = textureWidth - width;
int shiftedX = x + textureWidth - maskRight;
float uLocalDif = uDif * maskRight / textureWidth;
float uLocalMin;
float uLocalMax;
if (tilingDirection.right) {
uLocalMin = uMin;
uLocalMax = uMax - uLocalDif;
} else {
uLocalMin = uMin + uLocalDif;
uLocalMax = uMax;
}
for (int yTile = 0; yTile <= yTileCount; yTile++) {
int height = (yTile == yTileCount) ? yRemainder : textureHeight;
if (height == 0) {
//Note: We don't want to fully break out because our height will be zero if we are looking to
// draw the remainder, but there is no remainder as it divided evenly
break;
}
int y = yStart - ((yTile + 1) * textureHeight);
int maskTop = textureHeight - height;
float vLocalDif = vDif * maskTop / textureHeight;
float vLocalMin;
float vLocalMax;
if (tilingDirection.down) {
vLocalMin = vMin;
vLocalMax = vMax - vLocalDif;
} else {
vLocalMin = vMin + vLocalDif;
vLocalMax = vMax;
}
vertexBuffer.vertex(matrix4f, x, y + textureHeight, zLevel).uv(uLocalMin, vLocalMax).endVertex();
vertexBuffer.vertex(matrix4f, shiftedX, y + textureHeight, zLevel).uv(uLocalMax, vLocalMax).endVertex();
vertexBuffer.vertex(matrix4f, shiftedX, y + maskTop, zLevel).uv(uLocalMax, vLocalMin).endVertex();
vertexBuffer.vertex(matrix4f, x, y + maskTop, zLevel).uv(uLocalMin, vLocalMin).endVertex();
}
}
BufferUploader.drawWithShader(vertexBuffer.end());
if (blend) {
RenderSystem.disableBlend();
}
}
// reverse-order iteration over children w/ built-in GuiElement check, runs a basic anyMatch with checker
public static boolean checkChildren(List<? extends GuiEventListener> children, Predicate<GuiElement> checker) {
for (int i = children.size() - 1; i >= 0; i--) {
Object obj = children.get(i);
if (obj instanceof GuiElement element && checker.test(element)) {
return true;
}
}
return false;
}
public static int drawString(GuiGraphics guiGraphics, Font font, Component component, float x, float y, int color, boolean drawShadow) {
return guiGraphics.drawString(font, component.getVisualOrderText(), x, y, color, drawShadow);
}
public static void renderItem(GuiGraphics guiGraphics, @NotNull ItemStack stack, int xAxis, int yAxis, float scale, Font font, @Nullable String text, boolean overlay) {
if (!stack.isEmpty()) {
try {
PoseStack pose = guiGraphics.pose();
pose.pushPose();
//TODO - 1.20: Is this depth test stuff still needed?
RenderSystem.enableDepthTest();
if (scale != 1) {
//Translate before scaling, and then set xAxis and yAxis to zero so that we don't translate a second time
pose.translate(xAxis, yAxis, 0);
pose.scale(scale, scale, scale);
xAxis = 0;
yAxis = 0;
}
guiGraphics.renderItem(stack, xAxis, yAxis);
if (overlay) {
//When we render items ourselves in virtual slots or scroll slots we want to compress the z scale
// for rendering the stored count so that it doesn't clip with later windows
pose.translate(0, 0, -25);
guiGraphics.renderItemDecorations(font, stack, xAxis, yAxis, text);
}
RenderSystem.disableDepthTest();
pose.popPose();
} catch (Exception e) {
Mekanism.logger.error("Failed to render stack into gui: {}", stack, e);
}
}
}
/**
* Represents which direction our tiling is done when extending past the max size.
*/
public enum TilingDirection {
/**
* Textures are being tiled/filled from top left to bottom right.
*/
DOWN_RIGHT(true, true),
/**
* Textures are being tiled/filled from top right to bottom left.
*/
DOWN_LEFT(true, false),
/**
* Textures are being tiled/filled from bottom left to top right.
*/
UP_RIGHT(false, true),
/**
* Textures are being tiled/filled from bottom right to top left.
*/
UP_LEFT(false, false);
private final boolean down;
private final boolean right;
TilingDirection(boolean down, boolean right) {
this.down = down;
this.right = right;
}
}
}