From 9944a8252ab81884d4e134c84bf60e9fb401657e Mon Sep 17 00:00:00 2001 From: gigaherz Date: Tue, 5 Oct 2021 04:15:56 +0200 Subject: [PATCH] Implement GUI Stacking feature. (#8130) You can now use `mc.pushGuiLayer` and `mc.popGuiLayer` to manage the layers. mc.setScreen behaves such that if called with a non-null screen it replaces the entire stack, and if called with null it closes the entire stack. --- .../net/minecraft/client/Minecraft.java.patch | 12 +- .../client/gui/screens/Screen.java.patch | 9 ++ .../client/renderer/GameRenderer.java.patch | 14 +++ .../client/ForgeHooksClient.java | 73 +++++++++++- .../client/extensions/IForgeMinecraft.java | 39 ++++++ .../debug/client/GuiLayeringTest.java | 111 ++++++++++++++++++ src/test/resources/META-INF/mods.toml | 2 + 7 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/minecraftforge/client/extensions/IForgeMinecraft.java create mode 100644 src/test/java/net/minecraftforge/debug/client/GuiLayeringTest.java diff --git a/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/patches/minecraft/net/minecraft/client/Minecraft.java.patch index 9e206eb9ac..75bc654fe7 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/client/Minecraft.java +++ b/net/minecraft/client/Minecraft.java +@@ -257,7 +_,7 @@ + import org.lwjgl.util.tinyfd.TinyFileDialogs; + + @OnlyIn(Dist.CLIENT) +-public class Minecraft extends ReentrantBlockableEventLoop implements SnooperPopulator, WindowEventHandler { ++public class Minecraft extends ReentrantBlockableEventLoop implements SnooperPopulator, WindowEventHandler, net.minecraftforge.client.extensions.IForgeMinecraft { + private static Minecraft f_90981_; + private static final Logger f_90982_ = LogManager.getLogger(); + public static final boolean f_91002_ = Util.m_137581_() == Util.OS.OSX; @@ -397,14 +_,13 @@ this.f_91029_ = p_91084_.f_101908_.f_101928_; this.f_90986_ = p_91084_.f_101905_.f_101944_; @@ -154,10 +163,11 @@ if (p_91153_ == null && this.f_91073_ == null) { p_91153_ = new TitleScreen(); } else if (p_91153_ == null && this.f_91074_.m_21224_()) { -@@ -890,6 +_,14 @@ +@@ -890,6 +_,15 @@ } } ++ net.minecraftforge.client.ForgeHooksClient.clearGuiLayers(this); + Screen old = this.f_91080_; + net.minecraftforge.client.event.GuiOpenEvent event = new net.minecraftforge.client.event.GuiOpenEvent(p_91153_); + if (net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(event)) return; diff --git a/patches/minecraft/net/minecraft/client/gui/screens/Screen.java.patch b/patches/minecraft/net/minecraft/client/gui/screens/Screen.java.patch index c4e0980188..6e5095e9b3 100644 --- a/patches/minecraft/net/minecraft/client/gui/screens/Screen.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/screens/Screen.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/client/gui/screens/Screen.java +++ b/net/minecraft/client/gui/screens/Screen.java +@@ -125,7 +_,7 @@ + } + + public void m_7379_() { +- this.f_96541_.m_91152_((Screen)null); ++ this.f_96541_.popGuiLayer(); + } + + protected T m_142416_(T p_169406_) { @@ -163,7 +_,10 @@ } diff --git a/patches/minecraft/net/minecraft/client/renderer/GameRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/GameRenderer.java.patch index 779bfaca8d..cdd538d1fc 100644 --- a/patches/minecraft/net/minecraft/client/renderer/GameRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/GameRenderer.java.patch @@ -26,6 +26,20 @@ } } +@@ -840,11 +_,11 @@ + + Window window = this.f_109059_.m_91268_(); + RenderSystem.m_69421_(256, Minecraft.f_91002_); +- Matrix4f matrix4f = Matrix4f.m_162203_(0.0F, (float)((double)window.m_85441_() / window.m_85449_()), 0.0F, (float)((double)window.m_85442_() / window.m_85449_()), 1000.0F, 3000.0F); ++ Matrix4f matrix4f = Matrix4f.m_162203_(0.0F, (float)((double)window.m_85441_() / window.m_85449_()), 0.0F, (float)((double)window.m_85442_() / window.m_85449_()), 1000.0F, net.minecraftforge.client.ForgeHooksClient.getGuiFarPlane()); + RenderSystem.m_157425_(matrix4f); + PoseStack posestack = RenderSystem.m_157191_(); + posestack.m_166856_(); +- posestack.m_85837_(0.0D, 0.0D, -2000.0D); ++ posestack.m_85837_(0.0D, 0.0D, 1000F-net.minecraftforge.client.ForgeHooksClient.getGuiFarPlane()); + RenderSystem.m_157182_(); + Lighting.m_84931_(); + PoseStack posestack1 = new PoseStack(); @@ -879,7 +_,7 @@ } } else if (this.f_109059_.f_91080_ != null) { diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index 591156fe51..08914e5aa9 100644 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; +import net.minecraft.client.gui.chat.NarratorChatListener; import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.builders.LayerDefinition; @@ -127,7 +128,16 @@ import java.io.File; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.Stack; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -150,6 +160,55 @@ public class ForgeHooksClient //private static final ResourceLocation ITEM_GLINT = new ResourceLocation("textures/misc/enchanted_item_glint.png"); + /** + * Contains the *extra* GUI layers. + * The current top layer stays in Minecraft#currentScreen, and the rest serve as a background for it. + */ + private static final Stack guiLayers = new Stack<>(); + + public static void clearGuiLayers(Minecraft minecraft) + { + while(guiLayers.size() > 0) + popGuiLayerInternal(minecraft); + } + + private static void popGuiLayerInternal(Minecraft minecraft) + { + if (minecraft.screen != null) + minecraft.screen.removed(); + minecraft.screen = guiLayers.pop(); + } + + public static void pushGuiLayer(Minecraft minecraft, Screen screen) + { + if (minecraft.screen != null) + guiLayers.push(minecraft.screen); + minecraft.screen = Objects.requireNonNull(screen); + screen.init(minecraft, minecraft.getWindow().getGuiScaledWidth(), minecraft.getWindow().getGuiScaledHeight()); + NarratorChatListener.INSTANCE.sayNow(screen.getNarrationMessage()); + } + + public static void popGuiLayer(Minecraft minecraft) + { + if (guiLayers.size() == 0) + { + minecraft.setScreen(null); + return; + } + + popGuiLayerInternal(minecraft); + if (minecraft.screen != null) + NarratorChatListener.INSTANCE.sayNow(minecraft.screen.getNarrationMessage()); + } + + public static float getGuiFarPlane() + { + // 1000 units for the overlay background, + // and 2000 units for each layered Screen, + + return 1000.0F + 2000.0F * (1 + guiLayers.size()); + } + public static String getArmorTexture(Entity entity, ItemStack armor, String _default, EquipmentSlot slot, String type) { String result = armor.getItem().getArmorTexture(armor, entity, slot, type); @@ -310,6 +369,18 @@ public class ForgeHooksClient } public static void drawScreen(Screen screen, PoseStack mStack, int mouseX, int mouseY, float partialTicks) + { + mStack.pushPose(); + guiLayers.forEach(layer -> { + // Prevent the background layers from thinking the mouse is over their controls and showing them as highlighted. + drawScreenInternal(layer, mStack, Integer.MAX_VALUE, Integer.MAX_VALUE, partialTicks); + mStack.translate(0,0,2000); + }); + drawScreenInternal(screen, mStack, mouseX, mouseY, partialTicks); + mStack.popPose(); + } + + private static void drawScreenInternal(Screen screen, PoseStack mStack, int mouseX, int mouseY, float partialTicks) { if (!MinecraftForge.EVENT_BUS.post(new GuiScreenEvent.DrawScreenEvent.Pre(screen, mStack, mouseX, mouseY, partialTicks))) screen.render(mStack, mouseX, mouseY, partialTicks); diff --git a/src/main/java/net/minecraftforge/client/extensions/IForgeMinecraft.java b/src/main/java/net/minecraftforge/client/extensions/IForgeMinecraft.java new file mode 100644 index 0000000000..4314fd58a4 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/extensions/IForgeMinecraft.java @@ -0,0 +1,39 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.extensions; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.client.ForgeHooksClient; + +public interface IForgeMinecraft +{ + private Minecraft self() { return (Minecraft)this; } + + default void pushGuiLayer(Screen screen) + { + ForgeHooksClient.pushGuiLayer(self(), screen); + } + + default void popGuiLayer() + { + ForgeHooksClient.popGuiLayer(self()); + } +} diff --git a/src/test/java/net/minecraftforge/debug/client/GuiLayeringTest.java b/src/test/java/net/minecraftforge/debug/client/GuiLayeringTest.java new file mode 100644 index 0000000000..3804883eaa --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/client/GuiLayeringTest.java @@ -0,0 +1,111 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.util.Random; + +@Mod(GuiLayeringTest.MODID) +public class GuiLayeringTest +{ + private static final Random RANDOM = new Random(); + public static final String MODID = "gui_layer_test"; + + @Mod.EventBusSubscriber(value = Dist.CLIENT, modid = MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) + public static class ClientEvents + { + @SubscribeEvent + public static void guiOpen(GuiScreenEvent.InitGuiEvent event) + { + if (event.getGui() instanceof AbstractContainerScreen) + { + event.addWidget(new Button(2, 2, 150, 20, new TextComponent("Test Gui Layering"), btn -> { + Minecraft.getInstance().pushGuiLayer(new TestLayer(new TextComponent("LayerScreen"))); + })); + event.addWidget(new Button(2, 25, 150, 20, new TextComponent("Test Gui Normal"), btn -> { + Minecraft.getInstance().setScreen(new TestLayer(new TextComponent("LayerScreen"))); + })); + } + } + + public static class TestLayer extends Screen + { + protected TestLayer(Component titleIn) + { + super(titleIn); + } + + @Override + public void render(PoseStack poseStack, int mouseX, int mouseY, float partialTicks) + { + this.renderBackground(poseStack); + drawString(poseStack, this.font, this.title, this.width / 2, 15, 0xFFFFFF); + super.render(poseStack, mouseX, mouseY, partialTicks); + } + + @Override + protected void init() + { + int buttonWidth = 150; + int buttonHeight = 20; + int buttonGap = 4; + int buttonSpacing = (buttonHeight + buttonGap); + int buttons = 3; + + int xoff = (this.width - buttonWidth); + int yoff = (this.height - buttonHeight - buttonSpacing * (buttons - 1)); + int cnt = 0; + + xoff = RANDOM.nextInt(xoff); + yoff = RANDOM.nextInt(yoff); + + this.addRenderableWidget(new Button(xoff, yoff + buttonSpacing * (cnt++), buttonWidth, buttonHeight, new TextComponent("Push New Layer"), this::pushLayerButton)); + this.addRenderableWidget(new Button(xoff, yoff + buttonSpacing * (cnt++), buttonWidth, buttonHeight, new TextComponent("Pop Current Layer"), this::popLayerButton)); + this.addRenderableWidget(new Button(xoff, yoff + buttonSpacing * (cnt++), buttonWidth, buttonHeight, new TextComponent("Close entire stack"), this::closeStack)); + } + + private void closeStack(Button button) + { + this.minecraft.setScreen(null); + } + + private void popLayerButton(Button button) + { + this.minecraft.popGuiLayer(); + } + + private void pushLayerButton(Button button) + { + this.minecraft.pushGuiLayer(new TestLayer(new TextComponent("LayerScreen"))); + } + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index de881e034d..2b20890e77 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -156,6 +156,8 @@ license="LGPL v2.1" modId="custom_shield_test" [[mods]] modId="lazy_capabilities_on_items" +[[mods]] + modId="gui_layer_test" [[mods]] modId="add_pack_finders_test"