From a34752e2ee0e15a7a4feeae199f2e05998832450 Mon Sep 17 00:00:00 2001
From: Juuz <6596629+Juuxel@users.noreply.github.com>
Date: Wed, 27 Sep 2023 14:47:17 +0300
Subject: Texture: Support GUI sprites
---
.../cotton/gui/client/BackgroundPainter.java | 8 ++
.../cottonmc/cotton/gui/client/ScreenDrawing.java | 50 ++++++++++-
.../io/github/cottonmc/cotton/gui/widget/WBar.java | 12 ++-
.../cottonmc/cotton/gui/widget/data/Texture.java | 97 +++++++++++++++++++++-
4 files changed, 158 insertions(+), 9 deletions(-)
(limited to 'src/main/java/io')
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java b/src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java
index 52bd317..251fed7 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java
@@ -129,6 +129,9 @@ public interface BackgroundPainter {
/**
* Creates a new nine-patch background painter with a custom configuration.
*
+ *
This method cannot be used for {@linkplain Texture.Type#GUI_SPRITE GUI sprites}. Instead, you can use the
+ * vanilla nine-slice mechanism or use a standalone texture referring to the same file.
+ *
* @param texture the background painter texture
* @param configurator a consumer that configures the {@link NinePatch.Builder}
* @return the created nine-patch background painter
@@ -136,8 +139,13 @@ public interface BackgroundPainter {
* @see NinePatch
* @see NinePatch.Builder
* @see NinePatchBackgroundPainter
+ * @throws IllegalArgumentException when the texture is not {@linkplain Texture.Type#STANDALONE standalone}
*/
public static NinePatchBackgroundPainter createNinePatch(Texture texture, Consumer> configurator) {
+ if (texture.type() != Texture.Type.STANDALONE) {
+ throw new IllegalArgumentException("Non-standalone texture " + texture + " cannot be used for nine-patch");
+ }
+
TextureRegion region = new TextureRegion<>(texture.image(), texture.u1(), texture.v1(), texture.u2(), texture.v2());
var builder = NinePatch.builder(region);
configurator.accept(builder);
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java b/src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java
index 4064a04..7431597 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java
@@ -9,6 +9,7 @@ import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormats;
+import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.OrderedText;
import net.minecraft.text.Style;
import net.minecraft.util.Identifier;
@@ -105,7 +106,54 @@ public class ScreenDrawing {
* @since 3.0.0
*/
public static void texturedRect(DrawContext context, int x, int y, int width, int height, Texture texture, int color, float opacity) {
- texturedRect(context, x, y, width, height, texture.image(), texture.u1(), texture.v1(), texture.u2(), texture.v2(), color, opacity);
+ switch (texture.type()) {
+ // Standalone textures: convert into ID + UVs
+ case STANDALONE -> texturedRect(context, x, y, width, height, texture.image(), texture.u1(), texture.v1(), texture.u2(), texture.v2(), color, opacity);
+
+ // GUI sprites: Work more carefully as we need to support tiling/nine-slice
+ case GUI_SPRITE -> {
+ float r = (color >> 16 & 255) / 255.0F;
+ float g = (color >> 8 & 255) / 255.0F;
+ float b = (color & 255) / 255.0F;
+ RenderSystem.setShaderColor(r, g, b, opacity);
+
+ outer: if (texture.u1() == 0 && texture.u2() == 1 && texture.v1() == 0 && texture.v2() == 1) {
+ // If we're drawing the full texture, just let vanilla do it.
+ context.drawGuiTexture(texture.image(), x, y, width, height);
+ } else {
+ // If we're only drawing a region, draw the full texture in a larger size and clip it
+ // to only show the requested region.
+ float fullWidth = width / Math.abs(texture.u2() - texture.u1());
+ float fullHeight = height / Math.abs(texture.v2() - texture.v1());
+
+ // u1 == u2 or v1 == v2, we don't care about these situations.
+ if (Float.isInfinite(fullWidth) || Float.isInfinite(fullHeight)) break outer;
+
+ // Calculate the offset left/top coordinates.
+ int xo = x - (int) (fullWidth * texture.u1());
+ int yo = y - (int) (fullHeight * texture.v1());
+
+ MatrixStack matrices = context.getMatrices();
+ matrices.push();
+ matrices.translate(xo, yo, 0);
+
+ // Note: scale instead of drawing a (fullWidth, fullHeight) rectangle so that edges of nine-slice
+ // rectangles etc. are drawn scaled too. This matches the behavior of standalone textures.
+ matrices.scale(fullWidth / width, fullHeight / height, 1);
+
+ // Clip to the wanted area on the screen...
+ try (var frame = Scissors.push(x, y, width, height)) {
+ // ...and draw the texture.
+ context.drawGuiTexture(texture.image(), 0, 0, width, height);
+ }
+
+ matrices.pop();
+ }
+
+ // Don't let the color cause tinting to other draw calls.
+ RenderSystem.setShaderColor(1, 1, 1, 1);
+ }
+ }
}
/**
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java
index 0a4f51b..fad1579 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java
@@ -162,7 +162,8 @@ public class WBar extends WWidget {
int top = y + getHeight();
top -= barSize;
if (bar != null) {
- ScreenDrawing.texturedRect(context, left, top, getWidth(), barSize, bar.image(), bar.u1(), MathHelper.lerp(percent, bar.v2(), bar.v1()), bar.u2(), bar.v2(), 0xFFFFFFFF);
+ Texture clipped = bar.withUv(bar.u1(), MathHelper.lerp(percent, bar.v2(), bar.v1()), bar.u2(), bar.v2());
+ ScreenDrawing.texturedRect(context, left, top, getWidth(), barSize, clipped, 0xFFFFFFFF);
} else {
ScreenDrawing.coloredRect(context, left, top, getWidth(), barSize, ScreenDrawing.colorAtOpacity(0xFFFFFF, 0.5f));
}
@@ -170,7 +171,8 @@ public class WBar extends WWidget {
case RIGHT -> {
if (bar != null) {
- ScreenDrawing.texturedRect(context, x, y, barSize, getHeight(), bar.image(), bar.u1(), bar.v1(), MathHelper.lerp(percent, bar.u1(), bar.u2()), bar.v2(), 0xFFFFFFFF);
+ Texture clipped = bar.withUv(bar.u1(), bar.v1(), MathHelper.lerp(percent, bar.u1(), bar.u2()), bar.v2());
+ ScreenDrawing.texturedRect(context, x, y, barSize, getHeight(), clipped, 0xFFFFFFFF);
} else {
ScreenDrawing.coloredRect(context, x, y, barSize, getHeight(), ScreenDrawing.colorAtOpacity(0xFFFFFF, 0.5f));
}
@@ -178,7 +180,8 @@ public class WBar extends WWidget {
case DOWN -> {
if (bar != null) {
- ScreenDrawing.texturedRect(context, x, y, getWidth(), barSize, bar.image(), bar.u1(), bar.v1(), bar.u2(), MathHelper.lerp(percent, bar.v1(), bar.v2()), 0xFFFFFFFF);
+ Texture clipped = bar.withUv(bar.u1(), bar.v1(), bar.u2(), MathHelper.lerp(percent, bar.v1(), bar.v2()));
+ ScreenDrawing.texturedRect(context, x, y, getWidth(), barSize, clipped, 0xFFFFFFFF);
} else {
ScreenDrawing.coloredRect(context, x, y, getWidth(), barSize, ScreenDrawing.colorAtOpacity(0xFFFFFF, 0.5f));
}
@@ -189,7 +192,8 @@ public class WBar extends WWidget {
int top = y;
left -= barSize;
if (bar != null) {
- ScreenDrawing.texturedRect(context, left, top, barSize, getHeight(), bar.image(), MathHelper.lerp(percent, bar.u2(), bar.u1()), bar.v1(), bar.u2(), bar.v2(), 0xFFFFFFFF);
+ Texture clipped = bar.withUv(MathHelper.lerp(percent, bar.u2(), bar.u1()), bar.v1(), bar.u2(), bar.v2());
+ ScreenDrawing.texturedRect(context, left, top, barSize, getHeight(), clipped, 0xFFFFFFFF);
} else {
ScreenDrawing.coloredRect(context, left, top, barSize, getHeight(), ScreenDrawing.colorAtOpacity(0xFFFFFF, 0.5f));
}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/data/Texture.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/data/Texture.java
index e1265ce..67ee2a8 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/data/Texture.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/data/Texture.java
@@ -7,36 +7,100 @@ import java.util.Objects;
/**
* Represents a texture for a widget.
*
+ * Types
+ * Each texture has a type: it's either a {@linkplain Type#STANDALONE standalone texture file} or
+ * a {@linkplain Type#GUI_SPRITE sprite on the GUI sprite atlas}. Their properties are slightly different.
+ *
+ *
GUI sprites can use their full range of features such as tiling, stretching and nine-slice drawing modes,
+ * while standalone textures are only drawn stretched.
+ *
+ *
The format of the image ID depends on the type:
+ *
+ *
+ *
+ * Type |
+ * File path |
+ * Image ID |
+ *
+ *
+ *
+ *
+ * {@link Type#STANDALONE STANDALONE} |
+ * {@code assets/my_mod/textures/widget/example.png} |
+ * {@code my_mod:textures/widget/example.png} |
+ *
+ *
+ * {@link Type#GUI_SPRITE GUI_SPRITE} |
+ * {@code assets/my_mod/textures/gui/sprites/example.png} |
+ * {@code my_mod:example} |
+ *
+ *
+ *
+ *
+ * Note that the image ID can only be passed to non-{@code Texture} overloads of
+ * {@link io.github.cottonmc.cotton.gui.client.ScreenDrawing ScreenDrawing}.texturedRect()
+ * when the {@link #type() type} is {@link Type#STANDALONE}. GUI sprites need specialised code for drawing them,
+ * and they need to be drawn with specific {@code Texture}-accepting methods
+ * or {@link net.minecraft.client.gui.DrawContext}.
+ *
* @param image the image of this texture
+ * @param type the type of this texture
* @param u1 the start U-coordinate, between 0 and 1
* @param v1 the start V-coordinate, between 0 and 1
* @param u2 the end U-coordinate, between 0 and 1
* @param v2 the end V-coordinate, between 0 and 1
* @since 3.0.0
*/
-public record Texture(Identifier image, float u1, float v1, float u2, float v2) {
+public record Texture(Identifier image, Type type, float u1, float v1, float u2, float v2) {
/**
* Constructs a new texture that uses the full image.
*
* @param image the image
+ * @param type the type
+ * @throws NullPointerException if the image or the type is null
+ */
+ public Texture(Identifier image, Type type) {
+ this(image, type, 0, 0, 1, 1);
+ }
+
+ /**
+ * Constructs a new standalone texture with custom UV values.
+ *
+ * @param image the image of this texture
+ * @param u1 the start U-coordinate, between 0 and 1
+ * @param v1 the start V-coordinate, between 0 and 1
+ * @param u2 the end U-coordinate, between 0 and 1
+ * @param v2 the end V-coordinate, between 0 and 1
+ * @throws NullPointerException if the image is null
+ */
+ public Texture(Identifier image, float u1, float v1, float u2, float v2) {
+ this(image, Type.STANDALONE, u1, v1, u2, v2);
+ }
+
+ /**
+ * Constructs a new standalone texture that uses the full image.
+ *
+ * @param image the image
* @throws NullPointerException if the image is null
*/
public Texture(Identifier image) {
- this(image, 0, 0, 1, 1);
+ this(image, Type.STANDALONE, 0, 0, 1, 1);
}
/**
* Constructs a new texture with custom UV values.
*
* @param image the image
+ * @param type the type
* @param u1 the left U coordinate
* @param v1 the top V coordinate
* @param u2 the right U coordinate
* @param v2 the bottom V coordinate
- * @throws NullPointerException if the image is null
+ * @throws NullPointerException if the image or the type is null
*/
public Texture {
Objects.requireNonNull(image, "image");
+ Objects.requireNonNull(type, "type");
}
/**
@@ -49,6 +113,31 @@ public record Texture(Identifier image, float u1, float v1, float u2, float v2)
* @return the created texture
*/
public Texture withUv(float u1, float v1, float u2, float v2) {
- return new Texture(image, u1, v1, u2, v2);
+ return new Texture(image, type, u1, v1, u2, v2);
+ }
+
+ /**
+ * A {@link Texture}'s type. It represents the location of the texture.
+ *
+ * @since 9.0.0
+ */
+ public enum Type {
+ /**
+ * A texture in a standalone texture file.
+ *
+ *
The image IDs of standalone textures contain the full file path to the texture inside
+ * the {@code assets/} directory. For example, {@code my_mod:textures/widget/example.png} refers to
+ * {@code assets/my_mod/textures/widget/example.png}.
+ */
+ STANDALONE,
+
+ /**
+ * A texture in the GUI sprite atlas.
+ *
+ * The image IDs of GUI sprites only contain the subpath to the texture inside the sprite directory without
+ * the file extension. For example, {@code my_mod:example} refers to
+ * {@code assets/my_mod/textures/gui/sprites/example.png}.
+ */
+ GUI_SPRITE,
}
}
--
cgit