/**
Copyright (C) <2017> <coolAlias>
This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package zeldaswordskills.client.gui;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public abstract class AbstractGuiOverlay extends Gui implements IGuiOverlay
{
/** Default padding between elements */
protected static final int DEFAULT_PADDING = 2;
protected final Minecraft mc;
/** Max height and width for combining elements; based on ScaledResolution */
protected int maxH, maxW;
/** Left-most x, top-most y, width and height are usually re-set each time the overlay renders */
protected int x, y, width, height;
public AbstractGuiOverlay(Minecraft mc) {
this.mc = mc;
}
@Override
public int getLeft() {
return this.x;
}
@Override
public int getRight() {
return this.x + this.getWidth();
}
@Override
public int getTop() {
return this.y;
}
@Override
public int getBottom() {
return this.y + this.getHeight();
}
@Override
public int getWidth() {
return this.width;
}
@Override
public int getHeight() {
return this.height;
}
@Override
public HALIGN getHorizontalAlignment() {
return HALIGN.LEFT;
}
@Override
public VALIGN getVerticalAlignment() {
return VALIGN.TOP;
}
@Override
public boolean allowMergeX(boolean rendered) {
return true;
}
/**
* Sets up and dynamically adjusts element before calling {@link #render(ScaledResolution)}
*/
@Override
public boolean renderOverlay(ScaledResolution resolution, List<IGuiOverlay> overlays) {
this.maxW = resolution.getScaledWidth() / 3;
this.maxH = resolution.getScaledHeight() / 2;
this.setup(resolution);
for (IGuiOverlay overlay : overlays) {
if (this.intersectsWith(overlay) && !this.coalesce(overlay, resolution)) {
return false; // failed to render
}
}
this.render(resolution);
return true;
}
/**
* Called prior to {@link IGuiOverlay#renderOverlay} and after {@link IGuiOverlay#shouldRender()}.
* Set this element's x, y, width and height so they can be properly adjusted later.
*/
protected abstract void setup(ScaledResolution resolution);
/**
* Do the actual rendering for this element
*/
protected abstract void render(ScaledResolution resolution);
/**
* Returns {@link HALIGN#getOffset(int)}
*/
protected int getOffsetX(int offset) {
return this.getHorizontalAlignment().getOffset(offset);
}
/**
* Returns {@link VALIGN#getOffset(int)}
*/
protected int getOffsetY(int offset) {
return this.getVerticalAlignment().getOffset(offset);
}
/**
* Sets this element's X position based on its width, alignment and the provided offset
* @param offset Positive values move the element right, negative values move it left
*/
protected void setPosX(ScaledResolution resolution, int offset) {
switch (this.getHorizontalAlignment()) {
case LEFT:
this.x = offset;
break;
case CENTER:
this.x = ((resolution.getScaledWidth() / 2) - (this.getWidth() / 2)) + offset;
break;
case RIGHT:
this.x = resolution.getScaledWidth() - this.getWidth() + offset;
break;
}
}
/**
* Sets this element's Y position based on its height, alignment and the provided offset
* @param offset Positive values move the element down, negative values move it up
*/
protected void setPosY(ScaledResolution resolution, int offset) {
switch (this.getVerticalAlignment()) {
case TOP:
this.y = offset;
break;
case CENTER:
this.y = ((resolution.getScaledHeight() / 2) - (this.getHeight() / 2)) + offset;
break;
case BOTTOM:
this.y = resolution.getScaledHeight() - this.getHeight() + offset;
break;
}
}
/**
* Returns true if this element intersects with the passed in overlay
*/
public boolean intersectsWith(IGuiOverlay overlay) {
return this.overlapsX(overlay) && this.overlapsY(overlay);
}
/**
* Returns true if this element has any overlap with the given overlay on the x axis.
* This does not imply that the element intersects with the overlay.
*/
public boolean overlapsX(IGuiOverlay overlay) {
if (this.getLeft() > overlay.getRight() || this.getRight() < overlay.getLeft()) {
return false;
}
return true;
}
/**
* Returns true if this element has any overlap with the given overlay on the y axis.
* This does not imply that the element intersects with the overlay.
*/
public boolean overlapsY(IGuiOverlay overlay) {
if (this.getTop() > overlay.getBottom() || this.getBottom() < overlay.getTop()) {
return false;
}
return true;
}
/**
* Return true if this element can render on the same line as the existing overlay
*/
public boolean canCombineX(IGuiOverlay overlay) {
if (!this.allowMergeX(false) || !overlay.allowMergeX(true)) {
return false;
} else if (this.getHorizontalAlignment() != overlay.getHorizontalAlignment()) {
return false; // different horizontal alignments
} else if (!this.compareHeight(overlay)) {
return false; // element heights too different
}
switch (this.getHorizontalAlignment()) {
case LEFT: // left-most 1/3 of screen
return (overlay.getRight() + this.getWidth() + DEFAULT_PADDING) < this.maxW;
case CENTER: // center 1/3 of screen
return (overlay.getRight() + this.getWidth() + DEFAULT_PADDING) < (this.maxW * 2);
case RIGHT: // right-most 1/3 of screen, would shift left
return (overlay.getLeft() - this.getWidth() - DEFAULT_PADDING) > (this.maxW * 2);
}
return false;
}
/**
* Returns true if the this element's height is similar enough to the overlay's height
*/
protected boolean compareHeight(IGuiOverlay overlay) {
return this.getHeight() <= overlay.getHeight();
}
/**
* Adjusts this element's position so it does not overlap with the given overlay
*/
protected boolean coalesce(IGuiOverlay overlay, ScaledResolution resolution) {
if (this.canCombineX(overlay)) {
if (!this.shiftX(overlay, resolution)) {
return this.shiftY(overlay, resolution);
}
} else if (!this.shiftY(overlay, resolution)) {
return this.shiftX(overlay, resolution); // try shifting on X anyway
}
return true;
}
/**
* Adjust the x position of this element so it no longer collides with the given overlay
* @param overlay Element with collision on the x-axis
* @return true if this element's x position was adjusted
*/
protected boolean shiftX(IGuiOverlay overlay, ScaledResolution resolution) {
switch (this.getHorizontalAlignment()) {
case LEFT:
// Fall-through
case CENTER:
if (overlay.getRight() + this.getWidth() + DEFAULT_PADDING > resolution.getScaledWidth()) {
return false; // would shift fully or partially off screen
}
this.x = overlay.getRight() + DEFAULT_PADDING;
break;
case RIGHT:
if (overlay.getLeft() - (this.getWidth() + DEFAULT_PADDING) < 0) {
return false; // would shift fully or partially off screen
}
this.x = overlay.getLeft() - (this.getWidth() + DEFAULT_PADDING);
break;
}
return true;
}
/**
* Adjust the y position of this element so it no longer collides with the given overlay
* @param overlay Element with collision on the y-axis
* @return true if this element's y position was adjusted
*/
protected boolean shiftY(IGuiOverlay overlay, ScaledResolution resolution) {
switch (this.getVerticalAlignment()) {
case TOP:
// Fall-through
case CENTER:
if (overlay.getBottom() + this.getHeight() + DEFAULT_PADDING > (this.maxH * 2)) {
return false; // would shift fully or partially off screen
}
this.y = overlay.getBottom() + DEFAULT_PADDING;
break;
case BOTTOM:
if (overlay.getTop() - (this.getHeight() + DEFAULT_PADDING) < 0) {
return false; // would shift fully or partially off screen
}
this.y = overlay.getTop() - (this.getHeight() + DEFAULT_PADDING);
break;
}
return true;
}
}