package gminers.glasspane.component; import gminers.glasspane.GlassPane; import gminers.glasspane.GlassPaneMirror; import gminers.kitchensink.Rendering; import gminers.kitchensink.WaveType; import lombok.Getter; import lombok.Setter; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import com.gameminers.glasspane.internal.GlassPaneMod; /** * A pulsing component, to bring attention. May be useful to make buttons more apparent in a "first run" scenario.<br> * The bounds of the component are used as the 'core' area to pulsate around. The minimum distance the ring will pulsate to is around the * inner edge of the bounds of this component. When created, this component will default clipToSize to false due to this behavior.<br> * <br> * Illustration:<br> * <img src="http://dl.gameminers.com/blinker.png"/> * * @author Aesen Vismea * */ public class PaneBlinker extends ColorablePaneComponent { /** * Whether or not this component is currently blinking. */ @Getter @Setter private boolean blinking = true; /** * The maximum distance to pulsate to. */ @Getter @Setter private int distance = 10; /** * The wave this component will use for pulsating. */ @Getter @Setter private WaveType wave = WaveType.ABSOLUTE_TANGENT; /** * The speed that this component will pulsate at. */ @Getter @Setter private double speed = 4; private int tickCounter = 0; private PaneComponent target = null; public PaneBlinker() { setColor(0xFF0000); } @Override protected void doTick() { if (blinking) { tickCounter++; } if (target != null) { GlassPane targetPane = target.getGlassPane(); // be very sure we should be displaying // a blinker floating in the middle of nowhere will look strange if (target.isVisible() && targetPane != null && (Minecraft.getMinecraft().currentScreen == targetPane.getScreenMirror() || GlassPaneMod.inst.currentOverlays.contains(targetPane) || GlassPaneMod.inst.currentStickyOverlays .contains(targetPane))) { // mimic it's size, translation, and rotation setVisible(true); mimic(target); setTranslateX(target.getTranslateX()); setTranslateY(target.getTranslateY()); setRotationAllowed(target.isRotationAllowed()); setAngle(target.getAngle()); setXRot(target.getXRot()); setYRot(target.getYRot()); setZRot(target.getZRot()); } else { setVisible(false); } } } @Override protected void doRender(final int mouseX, final int mouseY, final float partialTicks) { if (blinking) { final double wv = wave.calculate((tickCounter + partialTicks) / speed); final int dist = (int)((float) (wv * distance)); final int col = color | (((int) ((1 - wv) * 255D) & 0xFF) << 24); Rendering.drawRect(-dist, -dist, width + dist, (-dist) + 1, col); Rendering.drawRect(-dist, (-dist) + 1, (-dist) + 1, height + dist, col); Rendering.drawRect((-dist) + 1, height + dist, width + dist, (height + dist) - 1, col); Rendering.drawRect(width + dist, (-dist) + 1, (width + dist) - 1, (height + dist) - 1, col); } } /** * Sets the target of this PaneBlinker to the passed component. A targeted blinker will track the position and size of the given * component and mimic it as closely as possible to keep up with the component. If the component becomes orphaned or hidden, the blinker * will go invisible.<br/> * Null is acceptable and disables targeting. */ public void target(PaneComponent component) { this.target = component; } /** * Searches for a component in the current overlay stack, displaying screen, modal overlays, etc, trying to find a component with a * matching name. * If it is found, {@link #target(PaneComponent)} is called with the found component. */ public void target(String componentName) { PaneComponent found = null; // we want to process this in the order they're rendered (roughly), to be somewhat intuitive // look through the overlays first for (GlassPane pane : GlassPaneMod.inst.currentOverlays) { found = search(componentName, pane); if (found != null) { break; } } // look through sticky overlays next, if needed if (found == null) { for (GlassPane pane : GlassPaneMod.inst.currentStickyOverlays) { found = search(componentName, pane); if (found != null) { break; } } } // now check the currently displaying screen if we still haven't found it if (found == null) { GuiScreen currentScreen = Minecraft.getMinecraft().currentScreen; if (currentScreen instanceof GlassPaneMirror) { GlassPaneMirror mirror = (GlassPaneMirror) currentScreen; found = search(componentName, mirror.getMirrored()); // still haven't found it? check underneath found = searchModal(componentName, mirror); } } // we tried to find your component, but it's not happening if (found == null) throw new IllegalArgumentException("Cannot find component with name '" + componentName + "' in the current Glass Pane display stack!"); // otherwise, we're ready // les do dis target(found); } // yeah, i'm using a stack-based search. // there should never be any hierarchies deep enough to cause a stack overflow... // but someone's probably going to find a way to do it anyway private PaneComponent searchModal(String needle, GlassPaneMirror haystack) { if (haystack.isModal()) { // same order as before for (GlassPane pane : haystack.getModalUnderlays()) { PaneComponent found = search(needle, pane); if (found != null) return found; } // check the pane itself GuiScreen underneath = haystack.getModal(); if (underneath instanceof GlassPaneMirror) { GlassPaneMirror underMirror = (GlassPaneMirror) underneath; PaneComponent found = search(needle, underMirror.getMirrored()); if (found != null) return found; // we have to go deeper found = searchModal(needle, underMirror); if (found != null) return found; } } return null; } private PaneComponent search(String needle, PaneContainer haystack) { for (PaneComponent pc : haystack.getComponents()) { if (needle.equals(pc.getName())) return pc; else if (pc instanceof PaneContainer) { PaneComponent found = search(needle, (PaneContainer) pc); if (found != null) return found; } } return null; } }