aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuuxel <6596629+Juuxel@users.noreply.github.com>2020-06-18 11:23:52 +0300
committerGitHub <noreply@github.com>2020-06-18 11:23:52 +0300
commit84ccb8d490227fe78711e11dec19e986df1a7ebf (patch)
treeafb92e035b4de1e243b4601870f4a36ad8ab35a7
parent6b613ed909e06f4d043f14d37db3c74b2fb14c86 (diff)
downloadLibGui-84ccb8d490227fe78711e11dec19e986df1a7ebf.tar.gz
LibGui-84ccb8d490227fe78711e11dec19e986df1a7ebf.tar.bz2
LibGui-84ccb8d490227fe78711e11dec19e986df1a7ebf.zip
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
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java5
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java4
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/Scissors.java147
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WClippedPanel.java17
4 files changed, 158 insertions, 15 deletions
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<T extends SyncedGuiDescription> 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<Frame> 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: <ul>
+ * <li>this frame is not on the stack, or</li>
+ * <li>this frame is not the topmost element on the stack</li>
+ * </ul>
+ * @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();
}
}