package gminers.glasspane; import gminers.glasspane.component.PaneContainer; import gminers.glasspane.event.KeyTypedEvent; import gminers.glasspane.event.MouseDownEvent; import gminers.glasspane.event.PaneDisplayEvent; import gminers.glasspane.event.PaneHideEvent; import gminers.glasspane.event.PaneOverlayEvent; import gminers.glasspane.shadowbox.AdaptivePanoramaShadowbox; import gminers.glasspane.shadowbox.ImageTileShadowbox; import gminers.glasspane.shadowbox.PaneShadowbox; import gminers.glasspane.shadowbox.PanoramaShadowbox; import gminers.kitchensink.Rendering; import java.util.List; import lombok.Getter; import lombok.Setter; import lombok.ToString; import net.minecraft.client.LoadingScreenRenderer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiIngame; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.util.ResourceLocation; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GL11; import com.gameminers.glasspane.internal.GlassPaneMod; import com.google.common.collect.Lists; /** * GlassPane is the basis of the Glass Pane GUI system, and can be used as either an overlay or standalone UI.<br/> * If you need to get a GuiScreen mirror of a GlassPane, use {@link #getScreenMirror}. * * @author Aesen Vismea */ @ToString public abstract class GlassPane extends PaneContainer { /** * A mirror of this GlassPane that can be used for APIs that need a GuiScreen, or for Minecraft itself. */ @Getter(lazy = true) private final GlassPaneMirror screenMirror = new GlassPaneMirror(this); /** * Whether or not the revert() method can be used. */ @Getter @Setter private boolean revertAllowed = false; protected static final ResourceLocation defaultShadowboxTex = new ResourceLocation( "textures/gui/options_background.png"); /** * The shadowbox (background) used by this GlassPane. * * @see ImageTileShadowbox * @see PanoramaShadowbox * @see AdaptivePanoramaShadowbox */ @Getter @Setter protected PaneShadowbox shadowbox = new ImageTileShadowbox(defaultShadowboxTex); private List<GlassPane> lastOverlays = null; private GuiScreen lastScreen = null; /** * Whether or not this GlassPane is currently being displayed with takeover mode. */ @Getter protected boolean takingOver = false; /** * Whether or not the shadowbox should be affected by rotation applied to this GlassPane. */ @Getter @Setter protected boolean shadowboxRotationAllowed = true; /** * Whether or not the screen should be cleared before drawing this GlassPane. If the screen size or rotation changes on the fly, this * will remove any artifacts left by the previous frame. */ @Getter @Setter protected boolean screenClearedBeforeDrawing = false; /** * Whether or not this GlassPane will render when the HUD is disabled. (Only applies if this GlassPane is displayed over a GuiIngame) */ @Getter @Setter protected boolean renderedWhenHUDIsOff = false; /** * Overrides the currently displaying GuiScreen with a screen dedicated to displaying this GlassPane, and stores the current GUI state * for later use with {@link #revert}.<br/> * Note: the screen displayed is the same as that returned by getScreenMirror. */ public final void show() { unsetModality(); _show(); } /** * Pushes this GlassPane onto the current GuiScreen's overlay stack. */ public final void overlay() { unsetModality(); // just add ourselves to the overlay list. this will put us on top since overlays are rendered in insertion-order. if modders need // something more complex, they can access currentOverlays directly. // yes, i am approving directly touching currentOverlays if you need it. just don't expect your code to work between Glass Pane // versions. it's in the internal package for a reason. GlassPaneMod.inst.currentOverlays.add(this); // fire an overlay event fireEvent(PaneOverlayEvent.class, this); // and now let's get the current screen size final Minecraft mc = Minecraft.getMinecraft(); // avoid creating a ScaledResolution if we can if (mc.currentScreen != null) { setWidth(mc.currentScreen.width); setHeight(mc.currentScreen.height); } else { // well, there's no screen currently displayed. we'll just use a ScaledResolution final ScaledResolution res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); setWidth(res.getScaledWidth()); setHeight(res.getScaledHeight()); } } /** * Pushes this GlassPane onto the global overlay stack. */ public final void stickyOverlay() { unsetModality(); GlassPaneMod.inst.currentStickyOverlays.add(this); // and now let's get the current screen size final Minecraft mc = Minecraft.getMinecraft(); // avoid creating a ScaledResolution if we can if (mc.currentScreen != null) { setWidth(mc.currentScreen.width); setHeight(mc.currentScreen.height); } else { // well, there's no screen currently displayed. we'll just use a ScaledResolution final ScaledResolution res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); setWidth(res.getScaledWidth()); setHeight(res.getScaledHeight()); } // fire an overlay event fireEvent(PaneOverlayEvent.class, this); } /** * Performs a modal overlay with this GlassPane.<br/> * A modal overlay renders a darkened background in front of the GUI that was the on top when modalOverlay() * was called. Any shadowboxes that the modal GlassPane has will be skipped.<br/> * It can be used to make dialogs, or similar things. */ public final void modalOverlay() { getScreenMirror().setModalUnderlays(Lists.newArrayList(GlassPaneMod.inst.currentOverlays)); getScreenMirror().setModal(Minecraft.getMinecraft().currentScreen); _show(); } private void _show() { // we offer a way to disable reverting if it's unnecessary, to save objects and cycles if (revertAllowed) { // first, save the current state of the gui lastScreen = Minecraft.getMinecraft().currentScreen; lastOverlays = Lists.newArrayList(GlassPaneMod.inst.currentOverlays); // copy it, since GlassPaneMod never gets rid of it's list } // then all we really need to do is display our mirror, the rest is handled by GlassPaneMod and GlassPaneMirror Minecraft.getMinecraft().displayGuiScreen(getScreenMirror()); } /** * Returns <code>this</code> - overridden for efficiency */ @Override public GlassPane getGlassPane() { return this; } /** * Uses this GlassPane to completely take over Minecraft's rendering. This can be used to display a GlassPane before Minecraft is * fully initialized.<br/> * <br/> * This method will block until another thread calls {@link #hide}. It is required to call this method from Minecraft's main thread, * since the OpenGL context is only accessible from that thread. * * @throws IllegalStateException * if Minecraft has finished initializing */ // TODO - Update this for new MouseUp, Wheel, etc events public final void takeover() { // make sure minecraft isn't fully initialized yet if (Minecraft.getMinecraft().theWorld != null || Minecraft.getMinecraft().currentScreen != null) throw new IllegalStateException("Minecraft is initialized!"); long lastTick = 0; // activate the takeover flag takingOver = true; // enter a loop final Minecraft mc = Minecraft.getMinecraft(); ScaledResolution res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); fireEvent(PaneDisplayEvent.class, this); while (takingOver) { // make sure Minecraft doesn't initialize while displaying this screen, as that would cause horrific flickering if (Minecraft.getMinecraft().theWorld != null || Minecraft.getMinecraft().currentScreen != null) throw new IllegalStateException("Minecraft is initialized!"); // tick if we should if (System.currentTimeMillis() - lastTick >= 50) { if (Mouse.isCreated()) { while (Mouse.next()) { final int mX = Mouse.getEventX() * width / mc.displayWidth; final int mY = height - Mouse.getEventY() * height / mc.displayHeight - 1; int button = Mouse.getEventButton(); if (Minecraft.isRunningOnMac && button == 0 && (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard .isKeyDown(Keyboard.KEY_RCONTROL))) { button = 1; } if (Mouse.getEventButtonState()) { fireEvent(MouseDownEvent.class, this, mX, mY, button); } } } if (Keyboard.isCreated()) { while (Keyboard.next()) { if (Keyboard.getEventKeyState()) { final int kCode = Keyboard.getEventKey(); final char kChar = Keyboard.getEventCharacter(); fireEvent(KeyTypedEvent.class, this, kChar, kCode); } } } tick(); lastTick = System.currentTimeMillis(); } // render GL11.glEnable(GL11.GL_TEXTURE_2D); res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); setWidth(res.getScaledWidth()); setHeight(res.getScaledHeight()); if (shadowbox != null) { boolean winch = false; if (shadowbox.getWidth() != res.getScaledWidth() || shadowbox.getHeight() != res.getScaledHeight()) { winch = true; } shadowbox.setWidth(res.getScaledWidth()); shadowbox.setHeight(res.getScaledHeight()); if (winch) { shadowbox.winch(); } } GL11.glViewport(0, 0, mc.displayWidth, mc.displayHeight); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GL11.glOrtho(0.0D, res.getScaledWidth(), res.getScaledHeight(), 0.0D, 1000.0D, 3000.0D); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); GL11.glTranslatef(0.0F, 0.0F, -2000.0F); GL11.glDisable(GL11.GL_LIGHTING); GL11.glDisable(GL11.GL_FOG); GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glEnable(GL11.GL_TEXTURE_2D); render(Mouse.getX() * res.getScaledWidth() / mc.displayWidth, res.getScaledHeight() - Mouse.getY() * res.getScaledHeight() / mc.displayHeight - 1, (System.currentTimeMillis() - lastTick) / 50f); GL11.glDisable(GL11.GL_LIGHTING); GL11.glDisable(GL11.GL_FOG); GL11.glEnable(GL11.GL_ALPHA_TEST); GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F); GL11.glFlush(); Display.update(); // resize if we need to if (!mc.isFullScreen() && Display.wasResized()) { final int oldWidth = mc.displayWidth; final int oldHeight = mc.displayHeight; final int newWidth = mc.displayWidth = Display.getWidth(); final int newHeight = mc.displayHeight = Display.getHeight(); if (newWidth != oldWidth || newHeight != oldHeight) { if (mc.displayWidth <= 0) { mc.displayWidth = 1; } if (mc.displayHeight <= 0) { mc.displayHeight = 1; } mc.displayWidth = newWidth <= 0 ? 1 : newWidth; mc.displayHeight = newHeight <= 0 ? 1 : newHeight; final ScaledResolution res1 = new ScaledResolution(mc, newWidth, newHeight); setWidth(res1.getScaledWidth()); setHeight(res1.getScaledHeight()); mc.loadingScreen = new LoadingScreenRenderer(mc); if (mc.entityRenderer != null) { mc.entityRenderer.updateShaderGroupSize(newWidth, newHeight); } } } if (Display.isCloseRequested()) { mc.shutdownMinecraftApplet(); } // use LWJGL's sync method to get the desired framerate Display.sync(30); } // draw the mojang logo again Rendering.drawFullScreenLogo(Rendering.MOJANG_LOGO, 0xFFFFFF); } /** * Reverts the GUI state to what was stored when this GUI was displayed with <code>show</code>.<br/> * If this GlassPane has never been displayed, does nothing. */ public final void revert() { // make sure we can do reverts if (!getScreenMirror().isModal() && !revertAllowed) throw new IllegalStateException("Attempt to use revert() on " + getClass().getName() + ", but it's not enabled!"); // then, make sure we have a state to revert to if (!getScreenMirror().isModal() && (lastOverlays == null || lastScreen == null)) throw new IllegalStateException("Attempt to use revert() on " + getClass().getName() + ", but there is no previous state to revert to!"); // if we're a modal overlay, let's use our modal metadata to revert instead final GuiScreen screen = getScreenMirror().isModal() ? getScreenMirror().getModal() : lastScreen; final List<GlassPane> overlays = getScreenMirror().isModal() ? getScreenMirror().getModalUnderlays() : lastOverlays; // now, display the previous screen Minecraft.getMinecraft().displayGuiScreen(screen); // and restore the overlays GlassPaneMod.inst.currentOverlays.clear(); if (overlays != null) { GlassPaneMod.inst.currentOverlays.addAll(overlays); } // and finally, invalidate our "previous" state since it's now current lastOverlays = null; lastScreen = null; getScreenMirror().setModal(null); getScreenMirror().setModalUnderlays(null); } /** * Removes this GlassPane from the overlay stack, if it's on it.<br/> * Also works in takeover mode to finish the takeover. */ public final void hide() { if (takingOver || GlassPaneMod.inst.currentOverlays.remove(this) || GlassPaneMod.inst.currentStickyOverlays.remove(this)) { fireEvent(PaneHideEvent.class, this); focusedComponent = null; } takingOver = false; } /** * Makes this GlassPane stop automatically overlaying the passed GlassPane, GuiScreen, or Object. * * @param screenOrPane * The class of the GUI to stop overlaying. * @throws IllegalArgumentException * If screenOrPane is not of a supported type. */ public final void stopOverlaying(final Class<?> screenOrPane) { // do we support the passed object? if (GlassPane.class.isAssignableFrom(screenOrPane) || GuiScreen.class.isAssignableFrom(screenOrPane) || screenOrPane == Object.class) { // fetch the list for this specific class final List<GlassPane> list = GlassPaneMod.inst.overlays.get(screenOrPane); if (list == null) // if there's no list, there's no overlays for this class, so just return return; // remove us from the list list.remove(this); // if the list is empty, dereference it if (list.isEmpty()) { GlassPaneMod.inst.overlays.remove(screenOrPane); } } else throw new IllegalArgumentException(screenOrPane.getName() + " is not supported by stopOverlaying!"); } /** * Makes this GlassPane automatically get pushed onto the overlay stack when any screen of the passed type is shown.<br/> * Works for GuiScreens or GlassPanes. Object can also be passed to overlay any and every screen, no matter it's type. GuiIngame can be * passed to overlay the ingame GUI. * * @param screenOrPane * The class of the GUI to overlay. * @throws IllegalArgumentException * If screenOrPane is not of a supported type. */ public final void autoOverlay(final Class<?> screenOrPane) { // do we support the passed object? this is to prevent people from overlaying random garbage if (GlassPane.class.isAssignableFrom(screenOrPane) || GuiScreen.class.isAssignableFrom(screenOrPane) || GuiIngame.class.isAssignableFrom(screenOrPane) || screenOrPane == Object.class) { // fetch the list for this specific class List<GlassPane> list; if (GlassPaneMod.inst.overlays.containsKey(screenOrPane) && GlassPaneMod.inst.overlays.get(screenOrPane) != null) { // if we already have a list, we'll use it list = GlassPaneMod.inst.overlays.get(screenOrPane); } else { // otherwise make a new one list = Lists.newArrayList(); GlassPaneMod.inst.overlays.put(screenOrPane, list); } // add us to the list list.add(this); } else throw new IllegalArgumentException(screenOrPane.getName() + " is not supported by autoOverlay!"); } /** * Makes this GlassPane stop automatically overriding the passed GlassPane or Object. * * @param screenOrPane * The class of the GUI to stop overriding. * @throws IllegalArgumentException * If screenOrPane is not of a supported type. */ public final void stopOverriding(final Class<?> screenOrPane) { // do we support the passed object? if (GlassPane.class.isAssignableFrom(screenOrPane) || GuiScreen.class.isAssignableFrom(screenOrPane)) { // are we the one overriding? if (GlassPaneMod.inst.overrides.get(screenOrPane) == this) { // if so, remove GlassPaneMod.inst.overrides.remove(screenOrPane); } else if (GlassPaneMod.inst.overrides.containsKey(screenOrPane)) { // otherwise, print a warning GlassPaneMod.inst.getLog().warn( "Attempting to stop overriding " + screenOrPane.getName() + " with " + this.getClass().getName() + " but it's actually overridden with " + GlassPaneMod.inst.overrides.get(screenOrPane).getClass().getName() + "!"); } } else throw new IllegalArgumentException(screenOrPane.getName() + " is not supported by stopOverriding!"); } /** * Makes this GlassPane automatically get displayed when any screen of the passed type is shown, but not this specific GlassPane.<br/> * Works for GuiScreens or GlassPanes. * * @param screenOrPane * The class of the GUI to override. * @throws IllegalArgumentException * If screenOrPane is not of a supported type. */ public final void autoOverride(final Class<?> screenOrPane) { // do we support the passed object? this is to prevent people from overriding random garbage if (GlassPane.class.isAssignableFrom(screenOrPane) || GuiScreen.class.isAssignableFrom(screenOrPane)) { // is it already being overridden? if so, print a warning if (GlassPaneMod.inst.overrides.containsKey(screenOrPane)) { GlassPaneMod.inst.getLog().warn( "CONFLICT: Overriding " + screenOrPane.getName() + " with " + this.getClass().getName() + " but it is already overridden with " + GlassPaneMod.inst.overrides.get(screenOrPane).getClass().getName() + "!"); } // now put it into the map GlassPaneMod.inst.overrides.put(screenOrPane, this); } else throw new IllegalArgumentException(screenOrPane.getName() + " is not supported by autoOverride!"); } /** * Internal method used by GlassPaneMod. Do not touch. Beware of dog. */ public void unsetModality() { getScreenMirror().setModal(null); getScreenMirror().setModalUnderlays(null); } }