@SideOnly(Side.CLIENT)
public abstract class Alignment {
	
	// TODO: Move some getChildPos / Size logic in here?
	
	/** Returns whether an element with this alignment can
	 *  set its size depending on its child elements. */
	public boolean canExpand() { return true; }
	
	public static class Min extends Alignment {
		public final int min;
		public Min(int min) { this.min = min; }
	}
	
	public static class Max extends Alignment {
		public final int max;
		public Max(int max) { this.max = max; }
	}
	
	public static class Both extends Alignment {
		public final int min, max;
		public Both(int min, int max) { this.min = min; this.max = max; }
		@Override public boolean canExpand() { return false; }
	}
	
	public static class Center extends Alignment {  }
	
} @SideOnly(Side.CLIENT)
public enum Direction {
	
	HORIZONTAL,
	VERTICAL;
	
	public Direction perpendicular()
		{ return (this == HORIZONTAL) ? VERTICAL : HORIZONTAL; }
	
} @SideOnly(Side.CLIENT)
public class GuiContainerScreen extends GuiScreen {
	
	private int _lastMouseX = -1;
	private int _lastMouseY = -1;
	
	public final GuiContext context;
	public final GuiContainer container;
	
	private GuiElementBase _tooltipElement = null;
	private long _tooltipTime = 0;
	
	public GuiContainerScreen() {
		context   = new GuiContext();
		container = new GuiContainer(context);
		container.setFill();
	}
	
	@Override
	public void setWorldAndResolution(Minecraft mc, int width, int height) {
		context.setScreen(this);
		super.setWorldAndResolution(mc, width, height);
		container.setSize(width, height);
	}
	
	@Override
	public void onGuiClosed() { context.setScreen(null); }
	
	
	@Override
	protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
		if (mouseButton == MouseButton.LEFT)
			context.setFocused(null); // TODO: Handle this more nicely.
		container.onMouseDown(mouseButton, mouseX, mouseY);
	}
	
	@Override
	public void handleMouseInput() throws IOException {
		super.handleMouseInput();
		
		int mouseX = Mouse.getEventX() * width / mc.displayWidth;
		int mouseY = height - Mouse.getEventY() * height / mc.displayHeight - 1;
		
		int scroll = Integer.signum(Mouse.getEventDWheel());
		if (scroll != 0) container.onMouseScroll(scroll, mouseX, mouseY);
	}
	
	@Override
	public void updateScreen() {
		int mouseX = Mouse.getX() * width / mc.displayWidth;
		int mouseY = height - Mouse.getY() * height / mc.displayHeight - 1;
		
		// Handle onMouseMove.
		if ((mouseX != _lastMouseX) || (mouseY != _lastMouseY)) {
			GuiElementBase pressed = context.getPressed();
			if (pressed != null) {
				ElementInfo info = ElementInfo.getElementHierarchy(pressed).getFirst();
				pressed.onMouseMove(mouseX - info.globalX, mouseY - info.globalY);
			} else if (container.contains(mouseX, mouseY))
				container.onMouseMove(mouseX, mouseY);
			_lastMouseX = mouseX;
			_lastMouseY = mouseY;
		}
		
		// Handle tooltips.
		GuiElementBase tooltipElement = new ElementInfo(container)
			.getElementsAt(mouseX, mouseY)
			.map(info -> info.element)
			.filter(element -> element.hasTooltip())
			.reduce((first, second) -> second) // Get the last element.
			.orElse(null);
		if (tooltipElement != _tooltipElement)
			_tooltipTime = Minecraft.getSystemTime();
		_tooltipElement = tooltipElement;
	}
	
	@Override
	protected void mouseReleased(int mouseX, int mouseY, int mouseButton) {
		if (mouseButton != MouseButton.LEFT) return; // TODO: Support other mouse buttons?
		GuiElementBase pressed = context.getPressed();
		if (pressed == null) return;
		pressed.onMouseUp(mouseButton, mouseX, mouseY);
		context.setPressed(null);
	}
	
	
	@Override
	protected void keyTyped(char typedChar, int keyCode) throws IOException {
		super.keyTyped(typedChar, keyCode);
		if (keyCode == Keyboard.KEY_F3) {
			if (isShiftKeyDown()) GuiContext.DEBUG = !GuiContext.DEBUG;
			else if (GuiContext.DEBUG) GuiContext.DEBUG_TEXT = !GuiContext.DEBUG_TEXT;
		}
		
		GuiElementBase focused = context.getFocused();
		if (focused != null) focused.onKey(keyCode, typedChar);
		// TODO: Implement tabbing?
	}
	
	
	@Override
	public void drawScreen(int mouseX, int mouseY, float partialTicks) {
		drawBackground(0);
		container.draw(mouseX, mouseY, partialTicks);
		
		if ((_tooltipElement != null) && (Minecraft.getSystemTime() >=
				_tooltipTime + _tooltipElement.getTooltipDelay()))
			_tooltipElement.drawTooltip(mouseX, mouseY, width, height, partialTicks);
		
		if (GuiContext.DEBUG)
			drawDebugInfo(mouseX, mouseY, partialTicks);
	}
	
	private void drawDebugInfo(int mouseX, int mouseY, float partialTicks) {
		FontRenderer fontRenderer = GuiElementBase.getFontRenderer();
		List<ElementInfo> hierarchy = new ElementInfo(container)
			.getElementsAt(mouseX, mouseY).collect(Collectors.toList());
		ElementInfo last = hierarchy.get(hierarchy.size() - 1);
		List<ElementInfo> children = last.getChildElements().collect(Collectors.toList());
		
		GuiElementBase.enableBlendAlphaStuffs();
		// Draw element boundaries.
		GuiElementBase.setRenderColorARGB(0x60FFFF00);
		for (ElementInfo info : hierarchy) if (info != last)
			GuiElementBase.drawOutline(info.globalX - 1, info.globalY - 1, info.width + 2, info.height + 2);
		// Draw selected element boundaries.
		GuiElementBase.setRenderColorARGB(0xA000FF00);
		GuiElementBase.drawOutline(last.globalX - 1, last.globalY - 1, last.width + 2, last.height + 2);
		GuiElementBase.setRenderColorARGB(0x3000FF00);
		GuiElementBase.drawRect(last.globalX, last.globalY, last.width, last.height);
		// Draw container padding.
		if (last.element instanceof GuiContainer) {
			GuiContainer container = (GuiContainer)last.element;
			int x = last.globalX; int y = last.globalY;
			int w = last.width;   int h = last.height;
			int padLeft   = container.getPaddingLeft();
			int padRight  = container.getPaddingRight();
			int padTop    = container.getPaddingTop();
			int padBottom = container.getPaddingBottom();
			GuiElementBase.setRenderColorARGB(0x800000FF);
			GuiElementBase.drawRect(x,                y,                 padLeft, h);
			GuiElementBase.drawRect(x + w - padRight, y,                 padRight, h);
			GuiElementBase.drawRect(x + padLeft,      y,                 w, padTop);
			GuiElementBase.drawRect(x + padLeft,      y + h - padBottom, w, padBottom);
		}
		// Draw child elements boundaries.
		if (!children.isEmpty()) {
			GuiElementBase.setRenderColorARGB(0x80FF0000);
			for (ElementInfo child : children)
				GuiElementBase.drawOutline(child.globalX - 1, child.globalY - 1, child.width + 2, child.height + 2);
		}
		GuiElementBase.disableBlendAlphaStuffs();
		
		if (!GuiContext.DEBUG_TEXT) return;
		// Draw debug text for elements in the hierarchy ..
		int textY = 4;
		for (ElementInfo info : hierarchy) {
			fontRenderer.drawStringWithShadow(info.toString(), 4, textY, 0xFFFFFF);
			textY += GuiElementBase.LINE_HEIGHT;
		}
		// .. and the child elements of the pointed-at element.
		if (!children.isEmpty()) {
			textY += GuiElementBase.LINE_HEIGHT;
			fontRenderer.drawStringWithShadow("Children:", 4, textY, 0xFFFFFF);
			textY += GuiElementBase.LINE_HEIGHT;
			for (ElementInfo child : children) {
				fontRenderer.drawStringWithShadow(child.toString(), 10, textY, 0xFFFFFF);
				textY += GuiElementBase.LINE_HEIGHT;
			}
		}
	}
	
} @SideOnly(Side.CLIENT)
public abstract class GuiElementBase {
	
	public static final String ELLIPSIS    = "...";
	public static final int ELLIPSIS_WIDTH = getStringWidth(ELLIPSIS);
	public static final int LINE_HEIGHT    = getFontRenderer().FONT_HEIGHT;
	
	public static final int COLOR_CONTROL           = 0xFFE0E0E0;
	public static final int COLOR_CONTROL_HIGHLIGHT = 0xFFFFFFA0;
	public static final int COLOR_CONTROL_DISABLED  = 0xFFA0A0A0;
	
	
	private GuiContext _context;
	private GuiContainer _parent;
	
	private boolean _visible = true, _enabled = true;

	private int _width, _height;
	
	private Alignment _horizontalAlign = new Alignment.Min(0);
	private Alignment _verticalAlign = new Alignment.Min(0);
	
	private List<String> _tooltip = null;
	private Supplier<List<String>> _tooltipFunc = null;
	private int _tooltipDelay = TooltipDelay.NORMAL;
	
	private int _dragStartX, _dragStartY;
	private int _dragPrevX, _dragPrevY;
	
	
	void setContext(GuiContext value) { _context = value; }
	void setParent(GuiContainer value) { _parent = value; }
	
	public final GuiContext getContext() { return _context; }
	public final GuiContainer getParent() { return _parent; }
	
	public boolean isVisible() { return _visible; }
	public void setVisible(boolean value) { _visible = value; }
	
	public boolean isEnabled() { return _enabled &&
		((getParent() != null) ? getParent().isEnabled() : true); }
	public void setEnabled(boolean value) { _enabled = value; }
	
	// Size related
	
	public int getSize(Direction direction)
		{ return (direction == Direction.HORIZONTAL) ? _width : _height; }
	public final int getWidth() { return getSize(Direction.HORIZONTAL); }
	public final int getHeight() { return getSize(Direction.VERTICAL); }
	
	public void setSize(Direction direction, int value) {
		if (value == getSize(direction)) return;
		if (direction == Direction.HORIZONTAL) _width = value;
		else _height = value;
		onSizeChanged(direction);
		if (_parent != null) _parent.onChildSizeChanged(this, direction);
	}
	public final void setWidth(int value) { setSize(Direction.HORIZONTAL, value); }
	public final void setHeight(int value) { setSize(Direction.VERTICAL, value); }
	public final void setSize(int width, int height) { setWidth(width); setHeight(height); }
	
	// Alignment related
	
	public Alignment getAlign(Direction direction)
		{ return (direction == Direction.HORIZONTAL) ? _horizontalAlign : _verticalAlign; }
	public final Alignment getHorizontalAlign() { return getAlign(Direction.HORIZONTAL); }
	public final Alignment getVerticalAlign() { return getAlign(Direction.VERTICAL); }
	
	public void setAlign(Direction direction, Alignment value) {
		if (value == null) throw new NullPointerException("Argument must be non-null");
		if (direction == Direction.HORIZONTAL) _horizontalAlign = value;
		else _verticalAlign = value;
		if (_parent != null) _parent.onChildAlignChanged(this, direction);
	}
	public final void setAlignHorizontal(Alignment value) { setAlign(Direction.HORIZONTAL, value); }
	public final void setAlignVertical(Alignment value) { setAlign(Direction.VERTICAL, value); }
	public final void setAlign(Alignment horizontal, Alignment vertical)
		{ setAlignHorizontal(horizontal); setAlignVertical(vertical); }
	
	// Useful alignment shortcuts
	
	public final void setPosition(int left, int top)
		{ setAlign(new Alignment.Min(left), new Alignment.Min(top)); }
	
	public final void setLeft(int value) { setAlignHorizontal(new Alignment.Min(value)); }
	public final void setRight(int value) { setAlignHorizontal(new Alignment.Max(value)); }
	public final void setTop(int value) { setAlignVertical(new Alignment.Min(value)); }
	public final void setBottom(int value) { setAlignVertical(new Alignment.Max(value)); }
	
	public final void setLeftRight(int value) { setLeftRight(value, value); }
	public final void setLeftRight(int left, int right)
		{ setAlignHorizontal(new Alignment.Both(left, right)); }
	public final void setTopBottom(int value) { setTopBottom(value, value); }
	public final void setTopBottom(int top, int bottom)
		{ setAlignVertical(new Alignment.Both(top, bottom)); }
	
	public final void setCenteredHorizontal() { setAlignHorizontal(new Alignment.Center()); }
	public final void setCenteredHorizontal(int width) { setCenteredHorizontal(); setWidth(width); }
	public final void setCenteredVertical() { setAlignVertical(new Alignment.Center()); }
	public final void setCenteredVertical(int height) { setCenteredVertical(); setHeight(height); }
	
	public final void setFillHorizontal() { setLeftRight(0); }
	public final void setFillVertical() { setTopBottom(0); }
	public final void setFill() { setFillHorizontal(); setFillVertical(); }
	
	// Tooltips
	
	public boolean hasTooltip() { return !getTooltip().isEmpty(); }
	
	public List<String> getTooltip() {
		List<String> tooltip = (_tooltipFunc != null) ? _tooltipFunc.get() : _tooltip;
		if (tooltip == null) tooltip = Collections.emptyList();
		return tooltip;
	}
	public int getTooltipDelay() { return _tooltipDelay; }
	
	public void setTooltip(List<String> value)
		{ setTooltip(TooltipDelay.NORMAL, value); }
	public void setTooltip(int delay, List<String> value)
		{ _tooltip = value; _tooltipDelay = delay; }
	public void setTooltip(Supplier<List<String>> value)
		{ setTooltip(TooltipDelay.NORMAL, value); }
	public void setTooltip(int delay, Supplier<List<String>> value)
		{ _tooltipFunc = value; _tooltipDelay = delay; }
	
	public static final class TooltipDelay {
		public static final int SHORT = 200;
		public static final int NORMAL = 400;
		public static final int LONG = 800;
		private TooltipDelay() {  }
	}
	
	// Element focus
	
	/** Returns if this element can be focused. */
	public boolean canFocus() { return false; }
	/** Returns if this element is currently focused. */
	public boolean isFocused() { return (getContext().getFocused() == this); }
	
	/** Makes this element the currently focused element. */
	public void setFocused() { setFocused(true); }
	/** Sets whether this element is the currently focused element. */
	public void setFocused(boolean value) {
		if (value && !canFocus())
			throw new UnsupportedOperationException("This element can't be focused");
		if (!isVisible()) return;
		getContext().setFocused(value ? this : null);
	}
	
	// Press / drag related
	
	/** Returns if this element can be pressed. */
	public boolean canPress() { return canDrag() || canFocus(); }
	/** Returns if this element is currently being pressed down. */
	public boolean isPressed() { return (getContext() != null) && (getContext().getPressed() == this); }
	/** Called when this element is pressed with the left mouse button.
	 *  Mouse position is relative to the element's position. */
	public void onPressed(int mouseX, int mouseY) {  }
	
	/** Returns if this element can be dragged. */
	public boolean canDrag() { return false; }
	/** Returns if this element is currently being dragged. */
	public boolean isDragged() { return (canDrag() && isPressed()); }
	/** Called when the mouse moves while this element is being dragged.
	 *  Mouse and start position are relative to the element's position.
	 *  Delta position is relative to the previous mouse position. */
	public void onDragged(int mouseX, int mouseY, int deltaX, int deltaY, int startX, int startY) {  }
	
	// Basic events
	
	/** Called when this element is resized. */
	public void onSizeChanged(Direction direction) {  }
	
	// Mouse events
	
	/** Called when the mouse is clicked in this element.
	 *  Mouse position is relative to the element's position.
	 *  Returns if the mouse action was handled. */
	public boolean onMouseDown(int mouseButton, int mouseX, int mouseY) {
		if (!isVisible() || !isEnabled()) return false;
		if ((mouseButton == MouseButton.LEFT) && canPress()) {
			GuiContext context = getContext();
			context.setPressed(this);
			if (canDrag()) {
				_dragStartX = mouseX; _dragStartY = mouseY;
				_dragPrevX  = mouseX; _dragPrevY  = mouseY;
			}
			if (canFocus()) setFocused();
			onPressed(mouseX, mouseY);
			return true;
		} else return false;
	}
	
	/** Called when the mouse is moved over this element or being dragged.
	 *  Mouse position is relative to the element's position. */
	public void onMouseMove(int mouseX, int mouseY) {
		if (isVisible() && isDragged()) {
			onDragged(mouseX, mouseY,
			          mouseX - _dragPrevX, mouseY - _dragPrevY,
			          _dragStartX, _dragStartY);
			_dragPrevX = mouseX; _dragPrevY = mouseY;
		}
	}
	
	/** Called when the mouse button is being released
	 *  while in this element or while it's being dragged.
	 *  Mouse position is relative to the element's position. */
	public void onMouseUp(int mouseButton, int mouseX, int mouseY) {  }
	
	/** Called when the mouse wheel is being scrolled over this element.
	 *  Scroll is either 1 (down) or -1 (up) depending on the scroll direction.
	 *  Mouse position is relative to the element's position. */
	public boolean onMouseScroll(int scroll, int mouseX, int mouseY) { return false; }
	
	// Keyboard events
	
	/** Called when this element is focused and a key is typed. */
	public void onKey(int keyCode, char keyChar) {  }
	
	// Draw events
	
	/** Draws this element on the screen. Rendered relative to its position.
	 *  Mouse position is relative to the element's position. */
	public void draw(int mouseX, int mouseY, float partialTicks) {  }
	
	/** Draws this element's tooltip on the screen. Rendering and mouse
	 *  position are global, not relative to this element's position. */
	public void drawTooltip(int mouseX, int mouseY, int screenWidth, int screenHeight, float partialTicks) {
		if (!hasTooltip()) return;
		GuiUtils.drawHoveringText(getTooltip(),
			mouseX, mouseY, screenWidth, screenHeight,
			300, getFontRenderer());
	}
	
	
	// Utility methods
	
	/** Returns whether the specified region (x, y, width, height)
	 *  contains the specified point (testX, testY). */
	public static boolean regionContains(int x, int y, int width, int height,
	                                     int testX, int testY) {
		return (testX >= x) && (testX < x + width) &&
		       (testY >= y) && (testY < y + height);
	}
	/** Returns whether the specified relative
	 *  position is within the element's region. */
	public boolean contains(int x, int y)
		{ return regionContains(0, 0, getWidth(), getHeight(), x, y); }
	
	/** Returns whether this element should be appear highlighted when drawn.
	 *  By default, the element must either be pressed down, or no other
	 *  element must be pressed down be while the cursor is over it. */
	public boolean isHighlighted(int mouseX, int mouseY) {
		GuiElementBase pressed = getContext().getPressed();
		return isEnabled() && (pressed == this)
		    || (pressed == null) && contains(mouseX, mouseY);
	}
	
	public static Minecraft getMC() { return Minecraft.getMinecraft(); }
	public static void display(GuiScreen screen) { getMC().displayGuiScreen(screen); }
	public static GuiScreen getCurrentScreen() { return getMC().currentScreen; }
	
	public static FontRenderer getFontRenderer() { return getMC().fontRenderer; }
	public static int getStringWidth(String text) { return getFontRenderer().getStringWidth(text); }
	
	public static void bindTexture(ResourceLocation resource)
		{ getMC().getTextureManager().bindTexture(resource); }
	
	public static void setRenderColor(float red, float green, float blue)
		{ setRenderColor(red, green, blue, 1.0F); }
	public static void setRenderColor(float red, float green, float blue, float alpha)
		{ GlStateManager.color(red, green, blue, alpha); }
	
	public static void setRenderColorRGB(int color)
		{ setRenderColorARGB(color | 0xFF000000); }
	public static void setRenderColorARGB(int color) {
		setRenderColor((color >> 16 & 0xFF) / 255.0f,
		               (color >> 8 & 0xFF) / 255.0f,
		               (color & 0xFF) / 255.0f,
		               (color >> 24 & 0xFF) / 255.0f); }
	
	public static void enableBlendAlphaStuffs() {
		GlStateManager.enableBlend();
		GlStateManager.tryBlendFuncSeparate(
			SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA,
			SourceFactor.ZERO, DestFactor.ONE);
		GlStateManager.disableTexture2D();
		GlStateManager.shadeModel(GL11.GL_SMOOTH);
	}
	public static void disableBlendAlphaStuffs() {
		GlStateManager.shadeModel(GL11.GL_FLAT);
		GlStateManager.enableTexture2D();
		GlStateManager.disableBlend();
	}
	
	public static void drawRect(int x, int y, int width, int height) {
		Tessellator tessellator = Tessellator.getInstance();
		BufferBuilder buffer = tessellator.getBuffer();
		buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
		buffer.pos(x        , y         , 0).endVertex();
		buffer.pos(x        , y + height, 0).endVertex();
		buffer.pos(x + width, y + height, 0).endVertex();
		buffer.pos(x + width, y         , 0).endVertex();
		tessellator.draw();
	}
	public static void drawOutline(int x, int y, int width, int height) {
		drawRect(x,             y,              width, 1);
		drawRect(x,             y + height - 1, width, 1);
		drawRect(x,             y + 1,          1,     height - 2);
		drawRect(x + width - 1, y + 1,          1,     height - 2);
	}
	
	public static void drawRect(int x, int y, int u, int v, int width, int height, int texSize)
		{ drawRect(x, y, u, v, width, height, texSize, texSize); }
	public static void drawRect(int x, int y, int u, int v, int width, int height, int texWidth, int texHeight) {
		float u1 = u / (float)texWidth;
		float v1 = v / (float)texHeight;
		float u2 = (u + width) / (float)texWidth;
		float v2 = (v + height) / (float)texHeight;
		drawRect(x, y, width, height, u1, v1, u2, v2);
	}
	public static void drawRect(int x, int y, int width, int height,
	                            float u1, float v1, float u2, float v2) {
		Tessellator tessellator = Tessellator.getInstance();
		BufferBuilder buffer = tessellator.getBuffer();
		buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
		buffer.pos(x        , y         , 0).tex(u1, v1).endVertex();
		buffer.pos(x        , y + height, 0).tex(u1, v2).endVertex();
		buffer.pos(x + width, y + height, 0).tex(u2, v2).endVertex();
		buffer.pos(x + width, y         , 0).tex(u2, v1).endVertex();
		tessellator.draw();
	}
	
	public static void drawColoredRectRGB(int x, int y, int width, int height, int color)
		{ drawColoredRectARGB(x, y, width, height, color | 0xFF000000); }
	public static void drawColoredRectARGB(int x, int y, int width, int height, int color)
		{ drawColoredRectARGB(x, y, width, height, color, color, color, color); }
	public static void drawColoredRectARGB(int x, int y, int width, int height,
		                                   int colorTL, int colorTR, int colorBL, int colorBR) {
		Tessellator tessellator = Tessellator.getInstance();
		BufferBuilder buffer = tessellator.getBuffer();
		buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
		buffer.pos(x        , y         , 0).color(colorTL >> 16 & 0xFF, colorTL >> 8 & 0xFF, colorTL & 0xFF, colorTL >> 24 & 0xFF).endVertex();
		buffer.pos(x        , y + height, 0).color(colorBL >> 16 & 0xFF, colorBL >> 8 & 0xFF, colorBL & 0xFF, colorBL >> 24 & 0xFF).endVertex();
		buffer.pos(x + width, y + height, 0).color(colorBR >> 16 & 0xFF, colorBR >> 8 & 0xFF, colorBR & 0xFF, colorBR >> 24 & 0xFF).endVertex();
		buffer.pos(x + width, y         , 0).color(colorTR >> 16 & 0xFF, colorTR >> 8 & 0xFF, colorTR & 0xFF, colorTR >> 24 & 0xFF).endVertex();
		tessellator.draw();
	}
	
	public static void drawColoredRectRGB(int x, int y, int width, int height,
	                                      float u1, float v1, float u2, float v2, int color)
		{ drawColoredRectARGB(x, y, width, height, u1, v1, u2, v2, color | 0xFF000000); }
	public static void drawColoredRectARGB(int x, int y, int width, int height,
	                                       float u1, float v1, float u2, float v2, int color)
		{ drawColoredRectARGB(x, y, width, height, u1, v1, u2, v2, color, color, color, color); }
	public static void drawColoredRectARGB(int x, int y, int width, int height,
	                                       float u1, float v1, float u2, float v2,
		                                   int colorTL, int colorTR, int colorBL, int colorBR) {
		Tessellator tessellator = Tessellator.getInstance();
		BufferBuilder buffer = tessellator.getBuffer();
		buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
		buffer.pos(x        , y         , 0).tex(u1, v1).color(colorTL >> 16 & 0xFF, colorTL >> 8 & 0xFF, colorTL & 0xFF, colorTL >> 24 & 0xFF).endVertex();
		buffer.pos(x        , y + height, 0).tex(u1, v2).color(colorBL >> 16 & 0xFF, colorBL >> 8 & 0xFF, colorBL & 0xFF, colorBL >> 24 & 0xFF).endVertex();
		buffer.pos(x + width, y + height, 0).tex(u2, v2).color(colorBR >> 16 & 0xFF, colorBR >> 8 & 0xFF, colorBR & 0xFF, colorBR >> 24 & 0xFF).endVertex();
		buffer.pos(x + width, y         , 0).tex(u2, v1).color(colorTR >> 16 & 0xFF, colorTR >> 8 & 0xFF, colorTR & 0xFF, colorTR >> 24 & 0xFF).endVertex();
		tessellator.draw();
	}
	
	// Utility classes
	
	public static final class MouseButton {
		private MouseButton() {  }
		
		public static int LEFT   = 0;
		public static int RIGHT  = 1;
		public static int MIDDLE = 2;
	}
	
	public static final class Color {
		private Color() {  }
		
		public static int WHITE       = 0xFFFFFFFF;
		public static int BLACK       = 0xFF000000;
		public static int TRANSPARENT = 0x00000000;
	}
	
} @SideOnly(Side.CLIENT)
public class GuiLabel extends GuiElementBase {
	
	public static final int SHADOW_DISABLED = -1;
	public static final int SHADOW_AUTOMATIC = -2;
	
	
	private String _text = "";
	private TextAlign _align = TextAlign.LEFT;
	private boolean _expands;
	
	private int _color         = Color.WHITE;
	private int _disabledColor = COLOR_CONTROL_DISABLED;
	private int _shadowColor   = SHADOW_AUTOMATIC;
	
	
	public GuiLabel(String text)
		{ this(text, TextAlign.LEFT); }
	public GuiLabel(String text, TextAlign align)
		{ this(0, 0, text, align); }
	public GuiLabel(int x, int y, String text)
		{ this(x, y, text, TextAlign.LEFT); }
	public GuiLabel(int x, int y, String text, TextAlign align) {
		_expands = true;
		setPosition(x, y);
		setText(text);
		setTextAlign(align);
	}
	
	public GuiLabel(int width, String text)
		{ this(width, text, TextAlign.LEFT); }
	public GuiLabel(int width, String text, TextAlign align)
		{ this(0, 0, width, text, align); }
	public GuiLabel(int x, int y, int width, String text)
		{ this(x, y, width, LINE_HEIGHT, text, TextAlign.LEFT); }
	public GuiLabel(int x, int y, int width, String text, TextAlign align)
		{ this(x, y, width, LINE_HEIGHT, text, align); }
	public GuiLabel(int x, int y, int width, int height, String text)
		{ this(x, y, width, height, text, TextAlign.LEFT); }
	public GuiLabel(int x, int y, int width, int height, String text, TextAlign align) {
		_expands = false;
		setPosition(x, y);
		setSize(width, height);
		setText(text);
		setTextAlign(align);
	}
	
	
	/** Returns if this label expands its size depending on text contents. */
	public boolean getExpands() { return _expands; }
	
	public TextAlign getTextAlign() { return _align; }
	public void setTextAlign(TextAlign value) { _align = value; }
	
	
	public String getText() { return _text; }
	public void setText(String text) {
		if (text == null) throw new NullPointerException("Argument text can't be null");
		if (text.equals(_text)) return;
		_text = text;
		if (_expands) {
			FontRenderer fontRenderer = getFontRenderer();
			int width  = fontRenderer.getStringWidth(_text);
			int height = fontRenderer.getWordWrappedHeight(_text, Integer.MAX_VALUE);
			setSize(width, height);
			_expands = true;
		}
	}
	
	public int getColor() { return _color; }
	public void setColor(int value) { _color = value; }
	
	public int getDisabledColor() { return _disabledColor; }
	public void setDisabledColor(int value) { _disabledColor = value; }
	
	public int getShadowColor() { return _shadowColor; }
	public void setShadowColor(int value) {
		if (value < SHADOW_AUTOMATIC) throw new IllegalArgumentException(
			String.format("'%d' is not a valid special shadow value", value));
		_shadowColor = value;
	}
	public void setShadowDisabled() { setShadowColor(SHADOW_DISABLED); }
	
	
	@Override
	public void onSizeChanged(Direction direction) {
		super.onSizeChanged(direction);
		_expands = false;
	}
	
	@Override
	public void draw(int mouseX, int mouseY, float partialTicks) {
		if (!isVisible()) return;
		FontRenderer fontRenderer = getFontRenderer();
		List<String> lines = fontRenderer.listFormattedStringToWidth(_text, Integer.MAX_VALUE);
		int yPos = 1;
		for (String line : lines) {
			int lineWidth = fontRenderer.getStringWidth(line);
			if (!_expands && (lineWidth > getWidth())) {
				line = fontRenderer.trimStringToWidth(line, getWidth() - ELLIPSIS_WIDTH) + ELLIPSIS;
				lineWidth = fontRenderer.getStringWidth(line);
			}
			int xPos = 0;
			switch (_align) {
				case LEFT:   xPos = 0; break;
				case RIGHT:  xPos = getWidth() - lineWidth; break;
				case CENTER: xPos = (getWidth() - lineWidth) / 2; break;
			}
			
			int color = isEnabled() ? getColor() : getDisabledColor();
			int shadowColor = getShadowColor();
			if (shadowColor >= 0)
				fontRenderer.drawString(line, xPos + 1, yPos + 1, shadowColor);
			if (shadowColor == SHADOW_AUTOMATIC)
				fontRenderer.drawStringWithShadow(line, xPos, yPos, color);
			else fontRenderer.drawString(line, xPos, yPos, color);
			
			yPos += LINE_HEIGHT;
		}
	}
	
	public enum TextAlign {
		LEFT,
		RIGHT,
		CENTER
	}
	
} @SideOnly(Side.CLIENT)
public class GuiLayout extends GuiContainer {
	
	public final Direction direction;
	
	private int[] _spacing = { 2 };
	
	public GuiLayout(Direction direction)
		{ this.direction = direction; }
	
	public int[] getSpacing() { return _spacing; }
	public void setSpacing(int... value) { _spacing = value; }
	
	
	// Getting / adding / inserting elements
	
	/** Gets the element at the specified index in this layout container. */
	public GuiElementBase get(int index)
		{ return children.get(index); }
	
	@Override
	public void add(GuiElementBase element) { addWeighted(element); }
	
	public void addFixed(GuiElementBase element, int size)
		{ insertFixed(children.size(), element, size); }
	public void addFixed(GuiElementBase element)
		{ insertFixed(children.size(), element); }
	
	public void addWeighted(GuiElementBase element)
		{ insertWeighted(children.size(), element); }
	public void addWeighted(GuiElementBase element, double weight)
		{ insertWeighted(children.size(), element, weight); }
	public void addWeighted(GuiElementBase element, double weight, int minSize)
		{ insertWeighted(children.size(), element, weight, minSize); }
	
	public void insertFixed(int index, GuiElementBase element, int size)
		{ element.setSize(direction, size); insertFixed(index, element); }
	public void insertFixed(int index, GuiElementBase element)
		{ insert(index, element, new LayoutAlignment.Fixed()); }
	
	public void insertWeighted(int index, GuiElementBase element)
		{ insertWeighted(index, element, 1.0); }
	public void insertWeighted(int index, GuiElementBase element, double weight)
		{ insertWeighted(index, element, weight, 0); }
	public void insertWeighted(int index, GuiElementBase element, double weight, int minSize)
		{ insert(index, element, new LayoutAlignment.Weighted(weight, minSize)); }
	
	protected void insert(int index, GuiElementBase element, LayoutAlignment alignment) {
		if (element.getContext() != null)
			throw new UnsupportedOperationException("The specified element already has a context set");
		element.setAlign(direction, alignment);
		super.insert(index, element);
	}
	
	
	@Override
	public void onChildSizeChanged(GuiElementBase element, Direction direction) {
		super.onChildSizeChanged(element, direction);
		if (direction == this.direction) updateChildSizes(direction);
	}
	
	@Override
	public void onChildAlignChanged(GuiElementBase element, Direction direction) {
		if ((direction == this.direction) && !(getAlign(direction) instanceof LayoutAlignment))
			throw new UnsupportedOperationException(
				"Unsupported Alignment '" + getAlign(direction).getClass() + "' in GuiLayout");
		super.onChildAlignChanged(element, direction);
	}
	
	@Override
	public int getChildPos(GuiElementBase element, Direction direction) {
		return (direction == this.direction)
			? getPaddingMin(direction) + ((LayoutAlignment)element.getAlign(direction))._childPos
			: super.getChildPos(element, direction);
	}
	
	
	@Override
	protected void updateChildSizes(Direction direction) {
		if (direction != this.direction)
			{ super.updateChildSizes(direction); return; }
		
		int[] spacing = getSpacing();
		double remainingWeight = 0.0;
		int availableSize = getSize(direction) - getPadding(direction);
		for (int i = 0; i < children.size(); i++) {
			GuiElementBase child = children.get(i);
			
			Alignment align = child.getAlign(direction);
			if (align instanceof LayoutAlignment.Weighted) {
				LayoutAlignment.Weighted weighted = (LayoutAlignment.Weighted)align;
				remainingWeight += weighted.weight;
				availableSize   -= weighted.minSize;
			} else availableSize -= child.getSize(direction);
			
			if (i < children.size() - 1)
				availableSize -= spacing[Math.min(spacing.length - 1, i)];
		}
		
		int currentPos = 0;
		for (int i = 0; i < children.size(); i++) {
			GuiElementBase child = children.get(i);
			
			int size = 0;
			LayoutAlignment align = (LayoutAlignment)child.getAlign(direction);
			if (align instanceof LayoutAlignment.Weighted) {
				LayoutAlignment.Weighted weighted = (LayoutAlignment.Weighted)align;
				size = Math.max(0, (int)(availableSize * weighted.weight / remainingWeight));
				remainingWeight -= weighted.weight;
				availableSize   -= size;
				size += weighted.minSize;
				child.setSize(direction, size);
			} else size = child.getSize(direction);
			align._childPos = currentPos;
			currentPos += size;
			
			if (i < children.size() - 1)
				currentPos += spacing[Math.min(spacing.length - 1, i)];
		}
		
		if (doesExpand(direction))
			setSize(direction, currentPos + getPadding(direction));
	}
	
	
	public static abstract class LayoutAlignment extends Alignment {
		
		private int _childPos;
		private LayoutAlignment() {  }
		
		public static final class Fixed extends LayoutAlignment {  }
		
		public static final class Weighted extends LayoutAlignment {
			public final double weight;
			public final int minSize;
			public Weighted(double weight, int minSize)
				{ this.weight = weight; this.minSize = minSize; }
			@Override public boolean canExpand() { return false; }
		}
		
	}
	
} @SideOnly(Side.CLIENT)
public class GuiScrollable extends GuiContainer {
	
	private static final int GRADIENT_SIZE = 4;
	
	public final EnumSet<Direction> directions;
	
	private GuiScrollbar _scrollbarHor, _scrollbarVer;
	private int _scrollAmount = 10;
	private int _scrollX = 0, _scrollY = 0;
	private int _scrollStartX, _scrollStartY;
	
	private int _contentWidth = 0, _contentHeight = 0;
	private int _contentWidthExtra = 0, _contentHeightExtra = 0;
	
	public GuiScrollable()
		{ this(EnumSet.allOf(Direction.class)); }
	public GuiScrollable(Direction direction)
		{ this(EnumSet.of(direction)); }
	public GuiScrollable(EnumSet<Direction> directions) {
		if (directions.isEmpty()) throw new IllegalArgumentException(
			"Argument 'directions' must be at least one Direction");
		this.directions = directions;
		for (Direction direction : directions) {
			GuiScrollbar scrollbar = new GuiScrollbar(direction);
			if (direction == Direction.HORIZONTAL)
				_scrollbarHor = scrollbar; else _scrollbarVer = scrollbar;
			add(scrollbar);
		}
	}
	
	
	public GuiScrollbar getScrollbar(Direction direction)
		{ return (direction == Direction.HORIZONTAL) ? _scrollbarHor : _scrollbarVer; }
	public final GuiScrollbar getScrollbar() {
		if (directions.size() != 1) throw new UnsupportedOperationException(
			"Element slides in both directions");
		return getScrollbar(directions.iterator().next());
	}
	
	public int getScroll(Direction direction)
		{ return (direction == Direction.HORIZONTAL) ? _scrollX : _scrollY; }
	public final int getScrollX() { return getScroll(Direction.HORIZONTAL); }
	public final int getScrollY() { return getScroll(Direction.VERTICAL); }
	
	public void setScroll(Direction direction, int value) {
		value = MathHelper.clamp(value, 0, getMaxScroll(direction));
		if (direction == Direction.HORIZONTAL) {
			if (value == _scrollX) return;
			_scrollX = value;
		} else {
			if (value == _scrollY) return;
			_scrollY = value;
		}
		onScrollChanged(direction);
	}
	public final void setScrollX(int value) { setScroll(Direction.HORIZONTAL, value); }
	public final void setScrollY(int value) { setScroll(Direction.VERTICAL, value); }
	
	public int getMaxScroll(Direction direction)
		{ return Math.max(0, getContentSize(direction) - getSize(direction)); }
	
	
	public int getContentSize(Direction direction)
		{ return getContentSize(direction, false); }
	public int getContentSize(Direction direction, boolean withoutPadding) {
		int size = (direction == Direction.HORIZONTAL)
			? _contentWidth : _contentHeight;
		if (!withoutPadding)
			size += (direction == Direction.HORIZONTAL)
				? _contentWidthExtra : _contentHeightExtra;
		return size;
	}
	
	
	protected void onScrollChanged(Direction direction) {  }
	
	
	@Override
	public boolean canDrag() { return true; }
	@Override
	public void onPressed(int mouseX, int mouseY) {
		_scrollStartX = _scrollX;
		_scrollStartY = _scrollY;
	}
	@Override
	public void onDragged(int mouseX, int mouseY, int deltaX, int deltaY, int startX, int startY) {
		setScrollX(_scrollStartX + (startX - mouseX));
		setScrollY(_scrollStartY + (startY - mouseY));
	}
	
	@Override
	public boolean onMouseScroll(int scroll, int mouseX, int mouseY) {
		if (isDragged()) return false;
		if (super.onMouseScroll(scroll, mouseX, mouseY)) return true;
		setScrollY(getScrollY() - scroll * _scrollAmount);
		return true;
	}
	
	@Override
	public int getChildPos(GuiElementBase element, Direction direction)
		{ return getChildPos(element, direction, true); }
	protected int getChildPos(GuiElementBase element, Direction direction, boolean scroll) {
		Alignment align = element.getAlign(direction);
		int scrollAmount = (scroll ? getScroll(direction) : 0);
		return (align instanceof ContentMax)
				? Math.min(getSize(direction) - element.getSize(direction),
				           getContentSize(direction, true) + ((ContentMax)align).margin)
			: (align instanceof Alignment.Max)
				? (align instanceof FixedMax ? getSize(direction)
					: getContentSize(direction) - scrollAmount - getPaddingMax(direction))
				- element.getSize(direction) - ((Alignment.Max)align).max
			: (align instanceof IFixedAlign)
				? super.getChildPos(element, direction) - getPaddingMin(direction)
			: (super.getChildPos(element, direction) - scrollAmount);
	}
	
	@Override
	protected void updateChildSizes(Direction direction) {
		for (GuiElementBase child : children) {
			Alignment align = child.getAlign(direction);
			if (align instanceof Alignment.Both) {
				Alignment.Both both = (Alignment.Both)align;
				int size = (both instanceof FixedBoth)
					? getSize(direction) : getContentSize(direction);
				child.setSize(direction, size - both.min - both.max);
			}
		}
	}
	
	@Override
	protected void expandToFitChildren(Direction direction) {
		int max   = 0;
		int extra = 0;
		for (GuiElementBase child : children) {
			Alignment align = child.getAlign(direction);
			if (align instanceof ContentMax)
				extra = Math.max(extra, ((ContentMax)align).margin + child.getSize(direction));
			else if (!(align instanceof Alignment.Both))
				max = Math.max(max, getChildPos(child, direction, false)
					- getPaddingMin(direction) + child.getSize(direction));
		}
		if (direction == Direction.HORIZONTAL)
			_contentWidth = max; else _contentHeight = max;
		extra += getPadding(direction);
		if (direction == Direction.HORIZONTAL)
			_contentWidthExtra = extra; else _contentHeightExtra = extra;
			
		if (getScroll(direction) > getMaxScroll(direction))
			setScroll(direction, getMaxScroll(direction));
	}
	
	@Override
	public void draw(int mouseX, int mouseY, float partialTicks) {
		if (!isVisible()) return;
		
		ElementInfo info = ElementInfo.getElementHierarchy(this).getFirst();
		getContext().pushScissor(info.globalX, info.globalY, getWidth(), getHeight());
		
		drawBackground(info.globalX + getScrollX(), info.globalY + getScrollY(),
		               mouseX, mouseY, partialTicks);
		
		// Skip drawing the scrollbars.
		foreachChildMousePos(mouseX, mouseY, (child, x, y, mx, my) -> {
			if (child instanceof GuiScrollbar) return;
			GlStateManager.pushMatrix();
			GlStateManager.translate(x, y, 0);
			child.draw(mx, my, partialTicks);
			GlStateManager.popMatrix();
		});
		
		drawForeground(mouseX, mouseY, partialTicks);
		
		getContext().popScissor();
	}
	
	public void drawBackground(int x, int y, int mouseX, int mouseY, float partialTicks) {
		float scale = 1 / 32.0F;
		float u1 = x * scale;
		float v1 = y * scale;
		float u2 = (x + getWidth()) * scale;
		float v2 = (y + getHeight()) * scale;
		
		bindTexture(Gui.OPTIONS_BACKGROUND);
		setRenderColorRGB(0x202020);
		drawRect(0, 0, getWidth(), getHeight(), u1, v1, u2, v2);
	}
	
	public void drawForeground(int mouseX, int mouseY, float partialTicks) {
		enableBlendAlphaStuffs();
		if (directions.contains(Direction.VERTICAL)) {
			drawColoredRectARGB(0, 0, getWidth(), GRADIENT_SIZE, Color.BLACK, Color.BLACK, Color.TRANSPARENT, Color.TRANSPARENT);
			drawColoredRectARGB(0, getHeight() - GRADIENT_SIZE, getWidth(), GRADIENT_SIZE, Color.TRANSPARENT, Color.TRANSPARENT, Color.BLACK, Color.BLACK);
		}
		if (directions.contains(Direction.HORIZONTAL)) {
			drawColoredRectARGB(0, 0, GRADIENT_SIZE, getHeight(), Color.BLACK, Color.TRANSPARENT, Color.BLACK, Color.TRANSPARENT);
			drawColoredRectARGB(getWidth() - GRADIENT_SIZE, 0, GRADIENT_SIZE, getHeight(), Color.BLACK, Color.TRANSPARENT, Color.BLACK, Color.TRANSPARENT);
		}
		disableBlendAlphaStuffs();
		
		// Draw only the scrollbars.
		foreachChildMousePos(mouseX, mouseY, (child, x, y, mx, my) -> {
			if (!(child instanceof GuiScrollbar)) return;
			GlStateManager.pushMatrix();
			GlStateManager.translate(x, y, 0);
			child.draw(mx, my, partialTicks);
			GlStateManager.popMatrix();
		});
	}
	
	
	public class GuiScrollbar extends GuiSlider {
		
		public static final int THICKNESS = 6;
		
		public final Direction direction;
		
		private int _scrollStart;
		
		private GuiScrollbar(Direction direction) {
			super(0, 0, (direction == Direction.HORIZONTAL) ? DEFAULT_WIDTH : THICKNESS,
			            (direction == Direction.VERTICAL)   ? DEFAULT_WIDTH : THICKNESS,
			      direction);
			this.direction = direction;
			setAlign(direction, new FixedBoth(0, 0));
			setAlign(direction.perpendicular(), new FixedMax(0));
			setSliderRaw(0);
		}
		
		@Override
		public int getSliderSize() {
			return MathHelper.clamp((int)Math.pow(GuiScrollable.this.getSize(direction), 2)
				/ getContentSize(direction), 32, getSize(direction) - 8);
		}
		
		@Override
		public double getSliderRaw(Direction direction)
			{ return (double)getScroll(direction) / getMaxScroll(direction); }
		
		@Override
		public void onPressed(int mouseX, int mouseY) {
			_scrollStart = getScroll(direction);
			super.onPressed(mouseX, mouseY);
		}
		@Override
		public void onDragged(int mouseX, int mouseY, int deltaX, int deltaY, int startX, int startY) {
			int diff = (direction == Direction.HORIZONTAL) ? (mouseX - startX) : (mouseY - startY);
			double scrollFactor = (double)(getSize(direction) - getSliderSize()) / Math.max(1, getMaxScroll(direction));
			setScroll(direction, _scrollStart + (int)(diff / scrollFactor));
		}
		
		@Override
		public String getValueText() { return ""; }
		
		@Override
		public void draw(int mouseX, int mouseY, float partialTicks) {
			if (!isVisible() || (getMaxScroll(direction) == 0)) return;
			
			int sliderSize = getSliderSize();
			int sliderPosition = (int)(getSliderRaw() * (getSize(direction) - sliderSize));
			int x, y, w, h;
			if (direction == Direction.HORIZONTAL) {
				x = sliderPosition; y = 0;
				w = sliderSize; h = getHeight();
			} else {
				x = 0; y = sliderPosition;
				w = getWidth(); h = sliderSize;
			}
			
			GlStateManager.disableTexture2D();
			drawColoredRectRGB(0, 0, getWidth(), getHeight(), Color.BLACK);
			drawColoredRectRGB(x, y, w, h, 0x808080);
			drawColoredRectRGB(x, y, w - 1, h - 1, 0xC0C0C0);
			GlStateManager.enableTexture2D();
		}
		
	}
	
	
	public interface IFixedAlign {  }
	
	public static class FixedMin extends Alignment.Min implements IFixedAlign
		{ public FixedMin(int min) { super(min); } }
	public static class FixedMax extends Alignment.Max implements IFixedAlign
		{ public FixedMax(int max) { super(max); } }
	public static class FixedBoth extends Alignment.Both implements IFixedAlign
		{ public FixedBoth(int min, int max) { super(min, max); } }
	
	public static class ContentMax extends Alignment {
		public final int margin;
		public ContentMax(int margin) { this.margin = margin; }
	}
	
} @SideOnly(Side.CLIENT)
public class GuiButton extends GuiElementBase {
	
	public static final ResourceLocation BUTTON_TEX = new ResourceLocation("textures/gui/widgets.png");
	public static final int DEFAULT_WIDTH  = 200;
	public static final int DEFAULT_HEIGHT = 20;
	public static final int MIN_TEXT_PADDING     = 6;
	public static final int DEFAULT_TEXT_PADDING = 20;
	
	
	private String _text     = "";
	private int _textColor   = -1;
	private Runnable _action = null;
	
	private boolean _isSwitch = false;
	private boolean _switchOn = false;
	
	public GuiButton() { this(DEFAULT_WIDTH); }
	public GuiButton(int width) { this(width, DEFAULT_HEIGHT); }
	public GuiButton(int width, int height) { this(width, height, ""); }
	
	public GuiButton(String text)
		{ this(getStringWidth(text) + DEFAULT_TEXT_PADDING, text); }
	public GuiButton(int width, String text)
		{ this(width, DEFAULT_HEIGHT, text); }
	public GuiButton(int width, int height, String text)
		{ this(0, 0, width, height, text); }
	
	public GuiButton(int x, int y, int width, int height, String text)
		{ setPosition(x, y); setSize(width, height); setText(text); }
	
	
	public String getText() { return _text; }
	public void setText(String value) {
		if (value == null) throw new NullPointerException("Argument can't be null");
		_text = value;
	}
	public final void addText(String value) {
		if (value == null) throw new NullPointerException("Argument can't be null");
		setText(getText() + value);
	}
	
	public int getTextColor() { return getTextColor(false); }
	public int getTextColor(boolean isHighlighted) {
		return (_textColor >= 0) ? _textColor
			: !isEnabled() ? COLOR_CONTROL_DISABLED
			: (isHighlighted) ? COLOR_CONTROL_HIGHLIGHT
			: COLOR_CONTROL;
	}
	public final void unsetTextColor() { setTextColor(-1); }
	public void setTextColor(int value) { _textColor = value; }
	
	public boolean isSwitch() { return _isSwitch; }
	public boolean isSwitchOn() { return _switchOn; }
	public void setSwitch() { setSwitch(false); }
	public void setSwitch(boolean value) { _isSwitch = true; _switchOn = value; }
	
	public void setAction(Runnable value) { _action = value; }
	
	
	@Override
	public boolean canPress() { return true; }
	
	@Override
	public void onPressed(int mouseX, int mouseY) {
		if (!isEnabled()) return;
		playPressSound();
		if (isSwitch()) setSwitch(!isSwitchOn());
		if (_action != null) _action.run();
	}
	
	public void playPressSound() {
		getMC().getSoundHandler().playSound(
			PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F));
	}
	
	
	// Drawing
	
	@Override
	public void draw(int mouseX, int mouseY, float partialTicks) {
		if (!isVisible()) return;
		boolean isHighlighted = isHighlighted(mouseX, mouseY);
		if (isSwitchOn()) {
			GlStateManager.disableCull();
			GlStateManager.pushMatrix();
			GlStateManager.translate(0, getHeight(), 0);
			GlStateManager.scale(1, -1, 1);
			drawButtonBackground(isHighlighted, partialTicks);
			GlStateManager.popMatrix();
			GlStateManager.enableCull();
			
			GlStateManager.pushMatrix();
			GlStateManager.translate(0, 1, 0);
			drawButtonForeground(isHighlighted, partialTicks);
			GlStateManager.popMatrix();
		} else {
			drawButtonBackground(isHighlighted, partialTicks);
			drawButtonForeground(isHighlighted, partialTicks);
		}
	}
	
	/** Draws the actual button texture. */
	protected void drawButtonBackground(boolean isHighlighted, float partialTicks) {
		int buttonIndex = !isEnabled() ? 0
		                : !isHighlighted ? 1
		                : 2;
		int ty = 46 + buttonIndex * 20;
		GuiUtils.drawContinuousTexturedBox(BUTTON_TEX, 0, 0, 0, ty, getWidth(), getHeight(),
		                                   DEFAULT_WIDTH, DEFAULT_HEIGHT, 2, 3, 2, 2, 0);
	}
	
	/** Draws the content on the button, such as text. */
	protected void drawButtonForeground(boolean isHighlighted, float partialTicks) {
		String text = getText();
		if (text.isEmpty()) return;
		FontRenderer fontRenderer = getFontRenderer();
		
		int textWidth = fontRenderer.getStringWidth(text);
		int maxTextWidth = getWidth() - MIN_TEXT_PADDING;
		if ((textWidth > maxTextWidth) && (textWidth > ELLIPSIS_WIDTH)) {
			text = fontRenderer.trimStringToWidth(text, maxTextWidth - ELLIPSIS_WIDTH).trim() + ELLIPSIS;
			textWidth = fontRenderer.getStringWidth(text);
		}
		
		fontRenderer.drawStringWithShadow(text, getWidth() / 2 - textWidth / 2,
		                                        (getHeight() - 8) / 2, getTextColor(isHighlighted));
	}
	
} @SideOnly(Side.CLIENT)
public class GuiButtonGlyph extends GuiButton {
	
	public static final int GLYPH_SPACING = getStringWidth(" ");
	public static final int DEFAULT_WIDTH = DEFAULT_HEIGHT;
	public static final float DEFAULT_GLYPH_SCALE = 2.0f;
	
	private String _glyph;
	private float _glyphScale;
	private int _glyphWidth;
	
	
	public GuiButtonGlyph(String glyph)
		{ this(glyph, DEFAULT_GLYPH_SCALE); }
	public GuiButtonGlyph(String glyph, float glyphScale)
		{ this(DEFAULT_WIDTH, glyph, glyphScale); }
	public GuiButtonGlyph(int width, String glyph, float glyphScale)
		{ this(width, DEFAULT_HEIGHT, glyph, glyphScale); }
	public GuiButtonGlyph(int width, int height, String glyph, float glyphScale)
		{ this(width, height, glyph, glyphScale, ""); }
	
	public GuiButtonGlyph(String glyph, String text)
		{ this((int)(getStringWidth(glyph) * 2.0f) + GLYPH_SPACING + getStringWidth(text) + DEFAULT_TEXT_PADDING, glyph, text); }
	public GuiButtonGlyph(int width, String glyph, String text)
		{ this(width, DEFAULT_HEIGHT, glyph, text); }
	public GuiButtonGlyph(int width, int height, String glyph, String text)
		{ this(width, height, glyph, DEFAULT_GLYPH_SCALE, text); }
	
	public GuiButtonGlyph(int width, String glyph, float glyphScale, String text)
		{ this(width, DEFAULT_HEIGHT, glyph, glyphScale, text); }
	public GuiButtonGlyph(int width, int height, String glyph, float glyphScale, String text)
		{ this(0, 0, width, height, glyph, glyphScale, text); }
	public GuiButtonGlyph(int x, int y, int width, int height, String glyph, float glyphScale, String text)
		{ super(x, y, width, height, text); setGlyph(glyph, glyphScale); }
	
	
	public String getGlyph() { return _glyph; }
	public float getGlyphScale() { return _glyphScale; }
	public int getGlyphWidth() { return _glyphWidth; }
	
	public void setGlyph(String glyph) { setGlyph(glyph, getGlyphScale()); }
	public void setGlyph(String glyph, float glyphScale) {
		_glyph = glyph;
		_glyphScale = glyphScale;
		_glyphWidth = (int)(getStringWidth(glyph) * glyphScale);
	}
	
	
	@Override
	protected void drawButtonForeground(boolean isHighlighted, float partialTicks) {
		String text = getText();
		FontRenderer fontRenderer = getFontRenderer();
		
		int contentWidth = getGlyphWidth();
		if (!text.isEmpty()) {
			int textWidth = fontRenderer.getStringWidth(text);
			int maxTextWidth = getWidth() - getGlyphWidth() - GLYPH_SPACING - MIN_TEXT_PADDING;
			if ((textWidth > maxTextWidth) && (textWidth > ELLIPSIS_WIDTH)) {
				text = fontRenderer.trimStringToWidth(text, maxTextWidth - ELLIPSIS_WIDTH).trim() + ELLIPSIS;
				textWidth = fontRenderer.getStringWidth(text);
			}
			contentWidth += GLYPH_SPACING + textWidth;
		}
		
		float scale = getGlyphScale();
		GlStateManager.pushMatrix();
		GlStateManager.scale(scale, scale, 1.0F);
		fontRenderer.drawStringWithShadow(getGlyph(),
			(int)((getWidth() - contentWidth) / 2 / scale),
			(int)((((getHeight() - 8) / scale / 2) - 1) / scale), getTextColor(isHighlighted));
		GlStateManager.popMatrix();
		
		if (!text.isEmpty())
			fontRenderer.drawStringWithShadow(text,
				(getWidth() - contentWidth) / 2 + getGlyphWidth() + GLYPH_SPACING,
				(getHeight() - 8) / 2, getTextColor(isHighlighted));
	}
	
} public enum ChangeRequiredAction {
	None,
	RejoinWorld,
	RestartMinecraft
}
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.GuiUtils;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.mcft.copy.backpacks.client.gui.Alignment;
import net.mcft.copy.backpacks.client.gui.Direction;
import net.mcft.copy.backpacks.client.gui.GuiContainerScreen;
import net.mcft.copy.backpacks.client.gui.GuiElementBase;
import net.mcft.copy.backpacks.client.gui.GuiLabel;
import net.mcft.copy.backpacks.client.gui.GuiLayout;
import net.mcft.copy.backpacks.client.gui.GuiScrollable;
import net.mcft.copy.backpacks.client.gui.control.GuiButton;
import net.mcft.copy.backpacks.client.gui.control.GuiButtonGlyph;
import net.mcft.copy.backpacks.config.Setting.ChangeRequiredAction;
package net.mcft.copy.backpacks.client.gui.config;




@SideOnly(Side.CLIENT)
public abstract class BaseConfigScreen extends GuiContainerScreen {
	
	private final List<GuiLabel> _titleLabels;