From 84ccb8d490227fe78711e11dec19e986df1a7ebf Mon Sep 17 00:00:00 2001 From: Juuxel <6596629+Juuxel@users.noreply.github.com> Date: Thu, 18 Jun 2020 11:23:52 +0300 Subject: Add a global scissor stack for nested scissor support (#59) * Add a global scissor stack for nested scissor support * Fix scissors not having proper dimensions, add check for negatives --- .../cotton/gui/client/CottonClientScreen.java | 5 +- .../cotton/gui/client/CottonInventoryScreen.java | 4 + .../cottonmc/cotton/gui/client/Scissors.java | 147 +++++++++++++++++++++ .../cottonmc/cotton/gui/widget/WClippedPanel.java | 17 +-- 4 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 src/main/java/io/github/cottonmc/cotton/gui/client/Scissors.java (limited to 'src/main/java/io') diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java index 6808aa1..81ff65a 100644 --- a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java +++ b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java @@ -10,6 +10,7 @@ import net.minecraft.text.Text; import io.github.cottonmc.cotton.gui.GuiDescription; import io.github.cottonmc.cotton.gui.widget.WPanel; import io.github.cottonmc.cotton.gui.widget.WWidget; +import org.lwjgl.opengl.GL11; public class CottonClientScreen extends Screen implements TextHoverRendererScreen { protected GuiDescription description; @@ -31,7 +32,6 @@ public class CottonClientScreen extends Screen implements TextHoverRendererScree public GuiDescription getDescription() { return description; } - @Override public void init(MinecraftClient client, int screenWidth, int screenHeight) { @@ -64,7 +64,10 @@ public class CottonClientScreen extends Screen implements TextHoverRendererScree if (description!=null) { WPanel root = description.getRootPanel(); if (root!=null) { + GL11.glEnable(GL11.GL_SCISSOR_TEST); root.paint(matrices, left, top, mouseX-left, mouseY-top); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + Scissors.checkStackIsEmpty(); } } diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java index 0b2daa1..b758a97 100644 --- a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java +++ b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java @@ -13,6 +13,7 @@ import org.lwjgl.glfw.GLFW; import io.github.cottonmc.cotton.gui.widget.WPanel; import io.github.cottonmc.cotton.gui.widget.WWidget; +import org.lwjgl.opengl.GL11; /** * A screen for a {@link SyncedGuiDescription}. @@ -222,7 +223,10 @@ public class CottonInventoryScreen extends Handl if (description!=null) { WPanel root = description.getRootPanel(); if (root!=null) { + GL11.glEnable(GL11.GL_SCISSOR_TEST); root.paint(matrices, x, y, mouseX-x, mouseY-y); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + Scissors.checkStackIsEmpty(); } } } diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/Scissors.java b/src/main/java/io/github/cottonmc/cotton/gui/client/Scissors.java new file mode 100644 index 0000000..a8f65c8 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/client/Scissors.java @@ -0,0 +1,147 @@ +package io.github.cottonmc.cotton.gui.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import org.lwjgl.opengl.GL11; + +import java.util.ArrayDeque; +import java.util.stream.Collectors; + +/** + * Contains a stack for GL scissors for restricting the drawn area of a widget. + * + * @since 2.0.0 + */ +@Environment(EnvType.CLIENT) +public final class Scissors { + private static final ArrayDeque STACK = new ArrayDeque<>(); + + private Scissors() { + } + + /** + * Pushes a new scissor frame onto the stack and refreshes the scissored area. + * + * @param x the frame's X coordinate + * @param y the frame's Y coordinate + * @param width the frame's width in pixels + * @param height the frame's height in pixels + * @return the pushed frame + */ + public static Frame push(int x, int y, int width, int height) { + Frame frame = new Frame(x, y, width, height); + STACK.push(frame); + refreshScissors(); + + return frame; + } + + /** + * Pops the topmost scissor frame and refreshes the scissored area. + * + * @throws IllegalStateException if there are no scissor frames on the stack + */ + public static void pop() { + if (STACK.isEmpty()) { + throw new IllegalStateException("No scissors on the stack!"); + } + + STACK.pop(); + refreshScissors(); + } + + private static void refreshScissors() { + MinecraftClient mc = MinecraftClient.getInstance(); + + if (STACK.isEmpty()) { + // Just use the full window framebuffer as a scissor + GL11.glScissor(0, 0, mc.getWindow().getFramebufferWidth(), mc.getWindow().getFramebufferHeight()); + return; + } + + int x = Integer.MIN_VALUE; + int y = Integer.MIN_VALUE; + int width = -1; + int height = -1; + + for (Frame frame : STACK) { + if (x < frame.x) { + x = frame.x; + } + if (y < frame.y) { + y = frame.y; + } + if (width == -1 || x + width > frame.x + frame.width) { + width = frame.width - (x - frame.x); + } + if (height == -1 || y + height > frame.y + frame.height) { + height = frame.height - (y - frame.y); + } + } + + int windowHeight = mc.getWindow().getHeight(); + double scale = mc.getWindow().getScaleFactor(); + int scaledWidth = (int) (width * scale); + int scaledHeight = (int) (height * scale); + + // Expression for Y coordinate adapted from vini2003's Spinnery (code snippet released under WTFPL) + GL11.glScissor((int) (x * scale), (int) (windowHeight - (y * scale) - scaledHeight), scaledWidth, scaledHeight); + } + + /** + * Internal method. Throws an {@link IllegalStateException} if the scissor stack is not empty. + */ + static void checkStackIsEmpty() { + if (!STACK.isEmpty()) { + throw new IllegalStateException("Unpopped scissor frames: " + STACK.stream().map(Frame::toString).collect(Collectors.joining(", "))); + } + } + + /** + * A single scissor frame in the stack. + */ + public static final class Frame implements AutoCloseable { + private final int x; + private final int y; + private final int width; + private final int height; + + private Frame(int x, int y, int width, int height) { + if (width < 0) throw new IllegalArgumentException("Negative width for a stack frame"); + if (height < 0) throw new IllegalArgumentException("Negative height for a stack frame"); + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /** + * Pops this frame from the stack. + * + * @throws IllegalStateException if: + * @see Scissors#pop() + */ + @Override + public void close() { + if (STACK.peekLast() != this) { + if (STACK.contains(this)) { + throw new IllegalStateException(this + " is not on top of the stack!"); + } else { + throw new IllegalStateException(this + " is not on the stack!"); + } + } + + pop(); + } + + @Override + public String toString() { + return "Frame{ at = (" + x + ", " + y + "), size = (" + width + ", " + height + ") }"; + } + } +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WClippedPanel.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WClippedPanel.java index d4cd9f5..531ae85 100644 --- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WClippedPanel.java +++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WClippedPanel.java @@ -1,8 +1,7 @@ package io.github.cottonmc.cotton.gui.widget; -import net.minecraft.client.MinecraftClient; +import io.github.cottonmc.cotton.gui.client.Scissors; import net.minecraft.client.util.math.MatrixStack; -import org.lwjgl.opengl.GL11; /** * A panel that is clipped to only render widgets inside its bounds. @@ -12,20 +11,10 @@ public class WClippedPanel extends WPanel { public void paint(MatrixStack matrices, int x, int y, int mouseX, int mouseY) { if (getBackgroundPainter()!=null) getBackgroundPainter().paintBackground(x, y, this); - GL11.glEnable(GL11.GL_SCISSOR_TEST); - MinecraftClient mc = MinecraftClient.getInstance(); - int rawHeight = mc.getWindow().getHeight(); - double scaleFactor = mc.getWindow().getScaleFactor(); - int scaledWidth = (int) (getWidth() * scaleFactor); - int scaledHeight = (int) (getHeight() * scaleFactor); - - // Expression for Y coordinate adapted from vini2003's Spinnery (code snippet released under WTFPL) - GL11.glScissor((int) (x * scaleFactor), (int) (rawHeight - (y * scaleFactor) - scaledHeight), scaledWidth, scaledHeight); - + Scissors.push(x, y, width, height); for(WWidget child : children) { child.paint(matrices, x + child.getX(), y + child.getY(), mouseX-child.getX(), mouseY-child.getY()); } - - GL11.glDisable(GL11.GL_SCISSOR_TEST); + Scissors.pop(); } } -- cgit