aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java8
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java50
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java12
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/data/Texture.java97
-rw-r--r--src/testMod/java/io/github/cottonmc/test/client/LibGuiTestClient.java1
-rw-r--r--src/testMod/java/io/github/cottonmc/test/client/TextureTestGui.java94
6 files changed, 253 insertions, 9 deletions
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.
*
+ * <p>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<NinePatch.Builder<Identifier>> configurator) {
+ if (texture.type() != Texture.Type.STANDALONE) {
+ throw new IllegalArgumentException("Non-standalone texture " + texture + " cannot be used for nine-patch");
+ }
+
TextureRegion<Identifier> 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.
*
+ * <h2>Types</h2>
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>The format of the image ID depends on the type:
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Type</th>
+ * <th>File path</th>
+ * <th>Image ID</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>{@link Type#STANDALONE STANDALONE}</td>
+ * <td>{@code assets/my_mod/textures/widget/example.png}</td>
+ * <td>{@code my_mod:textures/widget/example.png}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Type#GUI_SPRITE GUI_SPRITE}</td>
+ * <td>{@code assets/my_mod/textures/gui/sprites/example.png}</td>
+ * <td>{@code my_mod:example}</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <p>Note that the image ID can only be passed to non-{@code Texture} overloads of
+ * <code>{@link io.github.cottonmc.cotton.gui.client.ScreenDrawing ScreenDrawing}.texturedRect()</code>
+ * 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.
+ *
+ * <p>The image IDs of standalone textures contain the full file path to the texture inside
+ * the {@code assets/<namespace>} 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.
+ *
+ * <p>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,
}
}
diff --git a/src/testMod/java/io/github/cottonmc/test/client/LibGuiTestClient.java b/src/testMod/java/io/github/cottonmc/test/client/LibGuiTestClient.java
index 57c2790..a9500ae 100644
--- a/src/testMod/java/io/github/cottonmc/test/client/LibGuiTestClient.java
+++ b/src/testMod/java/io/github/cottonmc/test/client/LibGuiTestClient.java
@@ -68,6 +68,7 @@ public class LibGuiTestClient implements ClientModInitializer {
.then(literal("#196").executes(openScreen(client -> new Issue196TestGui())))
.then(literal("darkmode").executes(openScreen(client -> new DarkModeTestGui())))
.then(literal("titlealignment").executes(openScreen(Text.literal("test title"), client -> new TitleAlignmentTestGui())))
+ .then(literal("texture").executes(openScreen(client -> new TextureTestGui())))
));
}
diff --git a/src/testMod/java/io/github/cottonmc/test/client/TextureTestGui.java b/src/testMod/java/io/github/cottonmc/test/client/TextureTestGui.java
new file mode 100644
index 0000000..6925a3b
--- /dev/null
+++ b/src/testMod/java/io/github/cottonmc/test/client/TextureTestGui.java
@@ -0,0 +1,94 @@
+package io.github.cottonmc.test.client;
+
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription;
+import io.github.cottonmc.cotton.gui.widget.WGridPanel;
+import io.github.cottonmc.cotton.gui.widget.WLabeledSlider;
+import io.github.cottonmc.cotton.gui.widget.WPanel;
+import io.github.cottonmc.cotton.gui.widget.WSlider;
+import io.github.cottonmc.cotton.gui.widget.WSprite;
+import io.github.cottonmc.cotton.gui.widget.WTabPanel;
+import io.github.cottonmc.cotton.gui.widget.data.Axis;
+import io.github.cottonmc.cotton.gui.widget.data.Insets;
+import io.github.cottonmc.cotton.gui.widget.data.Texture;
+import io.github.cottonmc.cotton.gui.widget.icon.TextureIcon;
+
+import java.util.function.IntConsumer;
+
+public class TextureTestGui extends LightweightGuiDescription {
+ public TextureTestGui() {
+ WTabPanel root = new WTabPanel();
+
+ var panelSprite = new Texture(new Identifier("libgui:widget/panel_light"), Texture.Type.GUI_SPRITE);
+ var panelTexture = new Texture(new Identifier("libgui:textures/gui/sprites/widget/panel_light.png"), Texture.Type.STANDALONE);
+ var simpleSprite = new Texture(new Identifier("minecraft:icon/video_link"), Texture.Type.GUI_SPRITE);
+
+ root.add(createPanel(panelSprite), tab -> tab.icon(new TextureIcon(panelSprite)).tooltip(Text.literal("Nine-slice sprite")));
+ root.add(createPanel(simpleSprite), tab -> tab.icon(new TextureIcon(simpleSprite)).tooltip(Text.literal("Simple sprite")));
+ root.add(createPanel(panelTexture), tab -> tab.icon(new TextureIcon(panelTexture)).tooltip(Text.literal("Standalone")));
+ setRootPanel(root);
+ root.validate(this);
+ }
+
+ @Override
+ public void addPainters() {
+ // Remove tab panel background
+ }
+
+ private WPanel createPanel(Texture texture) {
+ WSprite sprite = new WSprite(texture);
+
+ WLabeledSlider red = new WLabeledSlider(0, 255, Axis.HORIZONTAL, Text.literal("Red"));
+ WLabeledSlider green = new WLabeledSlider(0, 255, Axis.HORIZONTAL, Text.literal("Green"));
+ WLabeledSlider blue = new WLabeledSlider(0, 255, Axis.HORIZONTAL, Text.literal("Blue"));
+ WLabeledSlider alpha = new WLabeledSlider(0, 255, Axis.HORIZONTAL, Text.literal("Alpha"));
+
+ red.setValue(255);
+ green.setValue(255);
+ blue.setValue(255);
+ alpha.setValue(255);
+
+ WSlider u1 = new WSlider(0, 100, Axis.HORIZONTAL);
+ WSlider u2 = new WSlider(0, 100, Axis.HORIZONTAL);
+ WSlider v1 = new WSlider(0, 100, Axis.VERTICAL);
+ WSlider v2 = new WSlider(0, 100, Axis.VERTICAL);
+
+ u2.setValue(100);
+ v2.setValue(100);
+
+ IntConsumer tintListener = unused -> {
+ sprite.setTint(blue.getValue() | (green.getValue() << 8) | (red.getValue() << 16) | (alpha.getValue() << 24));
+ };
+ red.setValueChangeListener(tintListener);
+ green.setValueChangeListener(tintListener);
+ blue.setValueChangeListener(tintListener);
+ alpha.setValueChangeListener(tintListener);
+
+ IntConsumer uvListener = unused -> {
+ sprite.setUv(u1.getValue() * 0.01f, v1.getValue() * 0.01f, u2.getValue() * 0.01f, v2.getValue() * 0.01f);
+ };
+ u1.setValueChangeListener(uvListener);
+ u2.setValueChangeListener(uvListener);
+ v1.setValueChangeListener(uvListener);
+ v2.setValueChangeListener(uvListener);
+
+ WGridPanel panel = new WGridPanel(20);
+ panel.setInsets(Insets.ROOT_PANEL);
+ panel.setGaps(3, 3);
+
+ panel.add(red, 0, 0, 3, 1);
+ panel.add(green, 3, 0, 3, 1);
+ panel.add(blue, 0, 1, 3, 1);
+ panel.add(alpha, 3, 1, 3, 1);
+
+ panel.add(u1, 2, 2, 4, 1);
+ panel.add(u2, 2, 3, 4, 1);
+ panel.add(v1, 0, 4, 1, 4);
+ panel.add(v2, 1, 4, 1, 4);
+
+ panel.add(sprite, 2, 4, 4, 4);
+ return panel;
+ }
+}