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: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
TypeFile pathImage 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