src/main/java/mnm/mods/util/gui/GuiComponent.java
public abstract class GuiComponent extends Gui {

    private boolean enabled = true;
    private boolean visible = true;
    private boolean hovered;
    private boolean focused;
    private int touchValue;
    private int lastButton;
    private long lastButtonTime;

    protected Minecraft mc = Minecraft.getMinecraft();

    private Color secondaryColor;
    private Color primaryColor;
    private GuiPanel parent;
    private ILocation location = new Location();
    private Dimension minimumSize = new Dimension();
    private float scale = 1;
    private ITextComponent caption;

    private EventBus bus = new EventBus((exception, context) -> LogManager.getLogger().throwing(exception));

    public GuiComponent() {
        this.bus.register(this);
    }

    /**
     * Draws this component on screen.
     *
     * @param mouseX The mouse x
     * @param mouseY The mouse y
     */
    public void drawComponent(int mouseX, int mouseY) {
    }

    public void drawCaption(int x, int y) {
        getCaptionText()
                .map(ITextComponent::getFormattedText)
                .filter(((Predicate<String>) String::isEmpty).negate())
                .filter(t -> this.isHovered())
                .ifPresent(text -> this.drawCaption(text, x, y));
    }

    protected void drawCaption(String caption, int x, int y) {
        caption = StringEscapeUtils.unescapeJava(caption);
        String[] list = caption.split("\n\r?");

        int w = 0;
        // find the largest width
        for (String s : list) {
            w = Math.max(w, (int) (mc.fontRenderer.getStringWidth(s) * getActualScale()));
        }
        y -= mc.fontRenderer.FONT_HEIGHT * list.length;

        Point point = getActualLocation().getPoint();
        ScaledResolution sr = new ScaledResolution(mc);
        int w2 = w;
        int x2 = x;
        while (x2 - 8 + point.x + w2 + 20 > sr.getScaledWidth()) {
            x--;
            w2--;
        }
        // put it on top
        GlStateManager.pushMatrix();
        Gui.drawRect(x - 2, y - 2, x + w + 2, y + mc.fontRenderer.FONT_HEIGHT * list.length + 1,
                0xcc333333);
        drawBorders(x - 2, y - 2, x + w + 2, y + mc.fontRenderer.FONT_HEIGHT * list.length + 1,
                0xccaaaaaa);
        for (String s : list) {
            mc.fontRenderer.drawStringWithShadow(s, x, y, this.getPrimaryColorProperty().getHex());
            y += mc.fontRenderer.FONT_HEIGHT;
        }
        GlStateManager.popMatrix();
    }

    protected void drawBorders(int x1, int y1, int x2, int y2, int color) {
        this.drawVerticalLine(x1 - 1, y1 - 1, y2 + 1, color); // left
        this.drawHorizontalLine(x1 - 1, x2, y1 - 1, color); // top
        this.drawVerticalLine(x2, y1 - 1, y2 + 1, color); // right
        this.drawHorizontalLine(x1, x2 - 1, y2, color); // bottom
    }

    /**
     * Draws borders around the provided points. Uses a brightened background
     * color with 0xaa transparency.
     *
     * @param x1 left point
     * @param y1 upper point
     * @param x2 right point
     * @param y2 lower point
     */
    protected void drawBorders(int x1, int y1, int x2, int y2) {
        Color color = getSecondaryColorProperty();
        int r = color.getRed();
        int g = color.getGreen();
        int b = color.getBlue();
        double amt = .75;
        r += luminance(r, amt);
        g += luminance(g, amt);
        b += luminance(b, amt);
        color = Color.of(r, g, b, 0xaa);
        drawBorders(x1, y1, x2, y2, color.getHex());
    }

    private static int luminance(int o, double amt) {
        return (int) ((255 - o) * amt);
    }

    /**
     * Updates the component. Called when it is called on the {@link GuiScreen}.
     */
    public void updateComponent() {
    }

    /**
     * Handles the mouse input and sends it to the mouse and action listeners.
     */
    public void handleMouseInput() {
        if (!isEnabled()) {
            this.hovered = false;
            return;
        }
        if (mc.currentScreen != null) {
            float scale = getActualScale();
            Point point = scalePoint(new Point(Mouse.getX(), Mouse.getY()), mc.currentScreen);
            ILocation actual = getActualLocation();
            // adjust for position and scale
            int x = (int) ((point.x - actual.getXPos()) / scale);
            int y = (int) ((point.y - actual.getYPos()) / scale);

            int button = Mouse.getEventButton();
            int scroll = Mouse.getEventDWheel();

            this.hovered = point.x >= actual.getXPos() && point.x <= actual.getXWidth()
                    && point.y >= actual.getYPos() && point.y <= actual.getYHeight();

            getBus().post(new GuiMouseEvent(this, MouseEvent.RAW, x, y, button, scroll));

            if (Mouse.getEventButtonState()) {
                if (this.mc.gameSettings.touchscreen && this.touchValue++ > 0) {
                    return;
                }
                lastButton = button;
                lastButtonTime = Minecraft.getSystemTime();
                if (isHovered()) {
                    getBus().post(new GuiMouseEvent(this, MouseEvent.CLICK, x, y, button, 0));
                    getBus().post(new ActionPerformedEvent(this));
                }
            } else if (button != -1) {
                if (this.mc.gameSettings.touchscreen && --this.touchValue > 0) {
                    return;
                }
                lastButton = -1;
                if (isHovered())
                    getBus().post(new GuiMouseEvent(this, MouseEvent.RELEASE, x, y, button, 0));
            } else if (lastButton != -1 && lastButtonTime > 0) {
                long buttonTime = Minecraft.getSystemTime() - this.lastButtonTime;
                getBus().post(new GuiMouseEvent(this, MouseEvent.DRAG, x, y, this.lastButton, buttonTime));
            }

            if (scroll != 0) {
                getBus().post(new GuiMouseEvent(this, MouseEvent.SCROLL, x, y, -1, -1, scroll));
            }
        }
    }

    /**
     * Handles the keyboard input and sends it to the keyboard listeners.
     */
    public void handleKeyboardInput() {
        int key = Keyboard.getEventKey();
        char character = Keyboard.getEventCharacter();
        long time = Keyboard.getEventNanoseconds();
        GuiKeyboardEvent event = new GuiKeyboardEvent(this, key, character, time);
        getBus().post(event);
    }

    /**
     * Called when the screen is closed.
     */
    public void onClosed() {
        this.hovered = false;
    }

    /**
     * Gets the current location of this component.
     *
     * @return The current immutable location.
     */
    public ILocation getLocation() {
        return this.location;
    }

    /**
     * Sets the location of this component. In order to maintain encapsulation,
     * it is wrapped as an immutable.
     *
     * @param location The new location
     */
    public void setLocation(ILocation location) {
        this.location = location.asImmutable();
    }

    /**
     * Gets the event bus used for the gui events for this component. This
     * replaces the listener interfaces previously used. They should easily port
     * over by adding a {@link com.google.common.eventbus.Subscribe} annotation
     * to the method.
     *
     * @return The event bus
     */
    public EventBus getBus() {
        return bus;
    }

    /**
     * Gets the parent of this component. Will return {@code null} until it is
     * added to a panel by being used as the parameter to
     * {@link GuiPanel#addComponent(GuiComponent)} or
     * {@link GuiPanel#addComponent(GuiComponent, Object)}.
     *
     * @return The parent or null if there is none
     */
    public Optional<GuiPanel> getParent() {
        return Optional.ofNullable(this.parent);
    }


    /**
     * Sets the parent of this component. Should only be used by
     * {@link GuiPanel}.
     *
     * @param guiPanel The parent
     */
    void setParent(@Nullable GuiPanel guiPanel) {
        this.parent = guiPanel;
    }

    /**
     * Gets the top-most parent of this component. May return null if this
     * component has no parent and is not a {@link GuiPanel}.
     *
     * @return The root panel or {@code null}.
     */
    private Optional<GuiPanel> getRootPanel() {
        Optional<GuiPanel> panel = getParent();
        while (true) {
            Optional<GuiPanel> parent = panel.flatMap(GuiComponent::getParent);
            if (!parent.isPresent())
                break;
            panel = parent;
        }
        if (panel.isPresent())
            return panel;
        else if (this instanceof GuiPanel)
            return Optional.of((GuiPanel) this);
        else
            return Optional.empty();
    }

    public ILocation getActualLocation() {
        Location location = this.getLocation().copy();
        location.scale(getActualScale());
        getParent().map(GuiComponent::getActualLocation).ifPresent(loc1 ->
                location.move(loc1.getXPos(), loc1.getYPos())
        );
        return location;
    }

    public void setMinimumSize(Dimension size) {
        this.minimumSize = size;
    }

    public Dimension getMinimumSize() {
        return minimumSize;
    }

    /**
     * Sets the scale for this component.
     *
     * @param scale The scale
     */
    public void setScale(float scale) {
        this.scale = scale;
    }

    /**
     * Gets the scale for this component.
     *
     * @return The scale
     */
    public float getScale() {
        return scale;
    }

    /**
     * Gets the total scale of this component. Takes into account all the
     * parents.
     *
     * @return The scale
     */
    protected float getActualScale() {
        return getScale() * getParent().map(GuiComponent::getActualScale).orElse(1F);
    }

    public Optional<Color> getPrimaryColor() {
        return Optional.ofNullable(this.primaryColor);
    }

    public void setPrimaryColor(@Nullable Color color) {
        this.primaryColor = color;
    }

    public Optional<Color> getSecondaryColor() {
        return Optional.ofNullable(this.secondaryColor);
    }

    public void setSecondaryColor(@Nullable Color color) {
        this.secondaryColor = color;
    }

    public Color getPrimaryColorProperty() {
        return getProperty(GuiComponent::getPrimaryColor, Color.WHITE);
    }

    public Color getSecondaryColorProperty() {
        return getProperty(GuiComponent::getSecondaryColor, Color.of(0));
    }

    /**
     * Gets if this is enabled. Disabled components will not handle mouse or
     * keyboard events.
     *
     * @return True if enabled, false if disabled.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Sets if this is enabled or not. Disabled components will not handle mouse
     * or keyboard events.
     *
     * @param enabled True for enabled, false for disabled
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * Gets if this component is visible. Non-visible components are not
     * rendered.
     *
     * @return The visibility state
     */
    public boolean isVisible() {
        return visible;
    }

    /**
     * Sets this component's visibility. Non-visible components are not
     * rendered.
     *
     * @param visible The visibility state
     */
    public void setVisible(boolean visible) {
        if (!visible) {
            this.onClosed();
        }
        this.visible = visible;
    }

    /**
     * Returns if the cursor is hovered over this component.
     *
     * @return THe hover state
     */
    public boolean isHovered() {
        return hovered && getParent().map(GuiComponent::isHovered).orElse(true);
    }

    public void setCaption(@Nullable ITextComponent text) {
        this.caption = text;
    }

    /**
     * Gets the caption which is shown when the mouse is hovering over this
     * component.
     *
     * @return The caption
     * @deprecated Use {@link #getCaptionText()} instead.
     */
    @Nullable
    @Deprecated
    public String getCaption() {
        return getCaptionText().map(ITextComponent::getFormattedText).orElse(null);
    }

    public Optional<ITextComponent> getCaptionText() {
        return Optional.ofNullable(caption);
    }

    /**
     * Gets the current focus of this component.
     *
     * @return The focus
     */
    public boolean isFocused() {
        return focused;
    }

    /**
     * Sets the component's focus
     *
     * @param focused The focus value
     */
    public void setFocused(boolean focused) {
        // check if is focusable
        if (isFocusable()) {
            if (focused) {
                // unfocus everything before focusing this one
                getRootPanel().ifPresent(GuiPanel::unfocusAll);
            }
            this.focused = focused;
        }
        // otherwise ignore
    }

    /**
     * Denotes if this is focusable. Override to change.
     *
     * @return Whether this is focusable
     */
    public boolean isFocusable() {
        return false;
    }

    private <T> T getProperty(final Function<GuiComponent, Optional<T>> prop, T def) {
        return getProperty(prop).orElse(def);
    }

    private <T> Optional<T> getProperty(final Function<GuiComponent, Optional<T>> prop) {
        Optional<T> result = prop.apply(this);
        Optional<GuiPanel> parent = getParent();
        if (!result.isPresent()) {
            result = parent.flatMap((GuiComponent p) -> p.getProperty(prop));
        }
        return result;
    }

    protected static Point scalePoint(Point point, GuiScreen screen) {
        Minecraft mc = Minecraft.getMinecraft();
        int x = point.x * screen.width / mc.displayWidth;
        int y = screen.height - point.y * screen.height / mc.displayHeight - 1;
        return new Point(x, y);
    }

    protected void drawModalCorners(TexturedModal modal) {
        ILocation location = getLocation();
        int x = 0;
        int y = 0;

        int w = location.getWidth() / 2;
        int w2 = location.getWidth() - w;
        int h = location.getHeight() / 2;
        int h2 = location.getHeight() - h + 1;

        int mx = modal.getXPos();
        int my = modal.getYPos();
        int mw = modal.getWidth() - w2;
        int mh = modal.getHeight() - h2;

        // bind the texture
        mc.getTextureManager().bindTexture(modal.getResourceLocation());

        // top left
        drawTexturedModalRect(x, y, mx, my, w, h);
        // top right
        drawTexturedModalRect(x + w, y, mx + mw, my, w2, h);
        // bottom left
        drawTexturedModalRect(x, y + h, mx, my + mh, w, h2);
        // bottom right
        drawTexturedModalRect(x + w, y + h, mx + mw, my + mh, w2, h2);

    }
}
src/main/java/mnm/mods/util/Color.java
public class Color {

    public static final Color BLACK = of(0xff000000);
    public static final Color DARK_BLUE = of(0xff0000aa);
    public static final Color DARK_GREEN = of(0xff00aa00);
    public static final Color DARK_AQUA = of(0xff00aaaa);
    public static final Color DARK_RED = of(0xffaa0000);
    public static final Color DARK_PURPLE = of(0xffaa00aa);
    public static final Color GOLD = of(0xffffaa00);
    public static final Color GRAY = of(0xffaaaaaa);
    public static final Color DARK_GRAY = of(0xff555555);
    public static final Color BLUE = of(0xff5555ff);
    public static final Color GREEN = of(0xff55ff55);
    public static final Color AQUA = of(0xff55ffff);
    public static final Color RED = of(0xffff5555);
    public static final Color LIGHT_PURPLE = of(0xffff55ff);
    public static final Color YELLOW = of(0xffffff55);
    public static final Color WHITE = of(0xffffffff);

    private static Random random = new Random();

    @SerializedName("r")
    private final int red;
    @SerializedName("g")
    private final int green;
    @SerializedName("b")
    private final int blue;
    @SerializedName("a")
    private final int alpha;

    /**
     * Creates a color using an html color code.
     *
     * @param hexColor The html color code
     */
    private Color(int hexColor) {
        this.alpha = hexColor >> 24 & 255;
        this.red = hexColor >> 16 & 255;
        this.green = hexColor >> 8 & 255;
        this.blue = hexColor & 255;
    }

    /**
     * Creates a color using RGBA color format.
     *
     * @param red Red color
     * @param green Green color
     * @param blue Blue color
     * @param alpha Transparency
     */
    private Color(int red, int green, int blue, int alpha) {
        this.red = red % 256;
        this.green = green % 256;
        this.blue = blue % 256;
        this.alpha = alpha % 256;
    }

    /**
     * Gets the html hex color code of this color usable by Minecraft's
     * {@link net.minecraft.client.gui.Gui}.
     *
     * @return The html hex code.
     */
    public int getHex() {
        int alphaI = alpha << 24;
        int redI = red << 16;
        int greenI = green << 8;
        return alphaI + redI + greenI + blue;
    }

    /**
     * Gets the red value.
     *
     * @return The red value
     */
    public int getRed() {
        return red;
    }

    /**
     * Gets the green value.
     *
     * @return The green value
     */
    public int getGreen() {
        return green;
    }

    /**
     * Gets the blue value.
     *
     * @return The blue value
     */
    public int getBlue() {
        return blue;
    }

    /**
     * Gets the alpha value.
     *
     * @return The alpha value
     */
    public int getAlpha() {
        return alpha;
    }

    /**
     * Convenience method for getting the html code for a RGBA color. Creates a
     * new object and calls {@link Color#getHex()}.
     *
     * @param red The red value
     * @param green The green value
     * @param blue The blue value
     * @param alpha The alpha value
     * @return The hexadecimal representation of this color.
     */
    public static int getColor(int red, int green, int blue, int alpha) {
        return of(red, green, blue, alpha).getHex();
    }

    /**
     * Generates a random color.
     *
     * @return A random color
     */
    public static Color random() {
        return of(random.nextInt());
    }

    public static Color of(int hexColor) {
        return new Color(hexColor);
    }

    public static Color of(int red, int green, int blue, int alpha) {
        return new Color(red, green, blue, alpha);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + alpha;
        result = prime * result + blue;
        result = prime * result + green;
        result = prime * result + red;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || !(obj instanceof Color))
            return false;
        Color other = (Color) obj;
        return alpha == other.alpha
                && blue == other.blue
                && green == other.green
                && red == other.red;
    }
}
src/main/java/mnm/mods/tabbychat/ChatChannel.java
public class ChatChannel implements Channel {

    public static final Channel DEFAULT_CHANNEL = new ChatChannel("*", false) {
        // Don't mess with this channel
        @Override
        public void setAlias(String alias) {}

        @Override
        public void setPrefix(String prefix) {}

        @Override
        public void setPrefixHidden(boolean hidden) {}

        @Override
        public void setCommand(String command) {}

        @Override
        public void openSettings() {
            // There are no settings for this channel
            TabbyChat.getInstance().openSettings(null);
        }
    };

    private transient List<Message> messages;

    private final String name;
    private final boolean isPm;
    private String alias;

    private String prefix = "";
    private boolean prefixHidden = false;

    private String command = "";

    private transient ChannelStatus status;

    ChatChannel(String name, boolean pm) {
        this.name = name;
        this.isPm = pm;
        this.alias = this.name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public boolean isPm() {
        return isPm;
    }

    @Override
    public String getAlias() {
        return this.alias;
    }

    @Override
    public void setAlias(String alias) {
        this.alias = alias;
    }

    @Override
    public String getPrefix() {
        return Strings.nullToEmpty(this.prefix);
    }

    @Override
    public void setPrefix(String prefix) {
        this.prefix = StringUtils.stripControlCodes(prefix);
    }

    @Override
    public boolean isPrefixHidden() {
        return this.prefixHidden;
    }

    @Override
    public void setPrefixHidden(boolean hidden) {
        this.prefixHidden = hidden;
    }

    @Override
    public String getCommand() {
        return Strings.nullToEmpty(command);
    }

    @Override
    public void setCommand(String command) {
        this.command = command;
    }

    @Override
    public ChannelStatus getStatus() {
        return status;
    }

    @Override
    public void setStatus(ChannelStatus status) {
        // priorities
        if (status == null || this.status == null
                || status.ordinal() < this.status.ordinal()) {
            this.status = status;
        }
        if (status == ChannelStatus.ACTIVE) {
            getManager().getChatBox().getChatArea().setChannel(this);
        }
    }

    @Override
    public void openSettings() {
        TabbyChat.getInstance().openSettings(new GuiSettingsChannel(this));
    }

    @Override
    public List<Message> getMessages() {
        if (messages == null) {
            // dumb gson
            messages = Collections.synchronizedList(Lists.<Message> newArrayList());
        }
        return messages;
    }

    @Override
    public void addMessage(ITextComponent chat) {
        addMessage(chat, 0);
    }

    @Override
    public void addMessage(ITextComponent chat, int id) {
        List<Channel> channels = TabbyChat.getInstance().getChat().getChannels();
        if (!channels.contains(this)) {
            TabbyChat.getInstance().getChat().addChannel(this);
        }
        if (id != 0) {
            removeMessages(id);
        }
        MessageAddedToChannelEvent event = new MessageAddedToChannelEvent(chat.createCopy(), id, this);
        TabbyChat.getInstance().getBus().post(event);
        if (event.text == null) {
            return;
        }
        if (TabbyChat.getInstance().settings.advanced.hideTag.get() && this != DEFAULT_CHANNEL) {
            ChannelPatterns pattern = TabbyChat.getInstance().serverSettings.general.channelPattern.get();
            Matcher matcher = pattern.getPattern().matcher(event.text.getUnformattedText());
            if (matcher.find()) {
                event.text = ChatTextUtils.subChat(event.text, matcher.end());
            }
        }

        int uc = Minecraft.getMinecraft().ingameGUI.getUpdateCounter();
        Message msg = new ChatMessage(uc, event.text, id, true);
        this.getMessages().add(0, msg);

        // compensate scrolling
        ChatArea chatbox = ((ChatManager) TabbyChat.getInstance().getChat()).getChatBox().getChatArea();
        if (getStatus() == ChannelStatus.ACTIVE && chatbox.getScrollPos() > 0 && id == 0) {
            chatbox.scroll(1);
        }

        trim(TabbyChat.getInstance().settings.advanced.historyLen.get());

        ((ChatManager) TabbyChat.getInstance().getChat()).save();
        dirty();
    }

    private void trim(int size) {
        Iterator<Message> iter = this.getMessages().iterator();

        for (int i = 0; iter.hasNext(); i++) {
            iter.next();
            if (i > size) {
                iter.remove();
            }
        }
    }

    @Override
    public void removeMessageAt(int pos) {
        this.getMessages().remove(pos);
        ((ChatManager) TabbyChat.getInstance().getChat()).save();
        dirty();
    }

    @Override
    public void removeMessages(int id) {
        this.getMessages().removeIf(msg -> msg.getID() == id);
        ((ChatManager) TabbyChat.getInstance().getChat()).save();
        dirty();
    }

    @Override
    public void clear() {
        this.getMessages().clear();
        dirty();
    }

    private void dirty() {
        if (this.getStatus() == ChannelStatus.ACTIVE)
            getManager().getChatBox().getChatArea().markDirty();
    }

    private static ChatManager getManager() {
        return (ChatManager) TabbyChat.getInstance().getChat();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (isPm ? 1231 : 1237);
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof ChatChannel))
            return false;
        ChatChannel other = (ChatChannel) obj;
        if (isPm != other.isPm)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return (isPm ? "@" : "#") + name;
    }

}
src/main/java/mnm/mods/util/TexturedModal.java
public class TexturedModal {

    private final ResourceLocation resource;
    private final int xPos;
    private final int yPos;
    private final int width;
    private final int height;

    public TexturedModal(ResourceLocation resource, int xPos, int yPos, int width, int height) {
        this.resource = resource;
        this.xPos = xPos;
        this.yPos = yPos;
        this.width = width;
        this.height = height;
    }

    public ResourceLocation getResourceLocation() {
        return resource;
    }

    public int getXPos() {
        return xPos;
    }

    public int getYPos() {
        return yPos;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

}
src/main/java/mnm/mods/util/gui/FlowLayout.java
public class FlowLayout implements ILayout {

    private List<GuiComponent> components = Lists.newArrayList();

    @Override
    public void addComponent(GuiComponent comp, Object constraints) {
        components.add(comp);
    }

    @Override
    public void removeComponent(GuiComponent comp) {
        components.remove(comp);
    }

    @Override
    public void layoutComponents(GuiPanel parent) {
        parent.getParent().ifPresent(p -> {
            int xPos = 0;
            int yPos = 0;
            int maxH = 0;
            for (GuiComponent comp : components) {
                Dimension size = comp.getMinimumSize();
                if (xPos + size.width >= p.getLocation().getWidth()) {
                    // wrapping
                    xPos = 0;
                    yPos += maxH;
                    maxH = 0;
                }
                comp.setLocation(new Location(xPos, yPos, size.width, size.height));

                maxH = Math.max(maxH, size.height);
                xPos += size.width;
            }
        });
    }

    @Override
    public Dimension getLayoutSize() {
        int width = 0;
        int height = 0;

        for (GuiComponent comp : components) {
            ILocation loc = comp.getLocation();
            width = Math.max(width, loc.getXPos() + loc.getWidth());
            height = Math.max(height, loc.getYPos() + loc.getHeight());
        }
        return new Dimension(width, height);
    }

}
src/main/java/mnm/mods/util/gui/GuiPanel.java
public class GuiPanel extends GuiComponent implements Iterable<GuiComponent> {

    private List<GuiComponent> components = Lists.newArrayList();
    private GuiComponent overlay;
    private ILayout layout;

    public GuiPanel(ILayout layout) {
        setLayout(layout);
    }

    public GuiPanel() {
    }

    @Subscribe
    public void unfocus(GuiMouseEvent event) {
        // Unfocuses all focusable on click
        if (event.getType() == MouseEvent.CLICK) {
            unfocusAll();
        }
    }

    @Override
    public void drawComponent(int mouseX, int mouseY) {
        if (this.getOverlay().isPresent()) {
            getOverlay().get().drawComponent(mouseX, mouseY);
            return;
        }
        getLayout().ifPresent(layout -> layout.layoutComponents(this));
        this.components.stream()
                .filter(GuiComponent::isVisible)
                .forEach(gc -> {
                    GlStateManager.pushMatrix();
                    ILocation location = gc.getLocation();
                    int xPos = location.getXPos();
                    int yPos = location.getYPos();
                    int x = (int) (mouseX / gc.getScale()) - xPos;
                    int y = (int) (mouseY / gc.getScale()) - yPos;
                    GlStateManager.translate(xPos, yPos, 0F);
                    GlStateManager.scale(gc.getScale(), gc.getScale(), 1F);
                    gc.drawComponent(x, y);

                    GlStateManager.popMatrix();
                });

        super.drawComponent(mouseX, mouseY);
    }

    @Override
    public void drawCaption(int mouseX, int mouseY) {
        super.drawCaption(mouseX, mouseY);
        if (this.getOverlay().isPresent()) {
            getOverlay().get().drawCaption(mouseX, mouseY);
            return;
        }
        this.components.stream()
                .filter(GuiComponent::isVisible)
                .forEach(gc -> {
                    GlStateManager.pushMatrix();
                    ILocation location = gc.getLocation();
                    int xPos = location.getXPos();
                    int yPos = location.getYPos();
                    int x = (int) (mouseX / gc.getScale()) - xPos;
                    int y = (int) (mouseY / gc.getScale()) - yPos;
                    GlStateManager.translate(xPos, yPos, 0F);
                    GlStateManager.scale(gc.getScale(), gc.getScale(), 1F);

                    gc.drawCaption(x, y);

                    GlStateManager.popMatrix();
                });
    }

    @Override
    public void updateComponent() {
        getOverlayOrChildren().forEach(GuiComponent::updateComponent);

    }

    @Override
    public void handleMouseInput() {
        super.handleMouseInput();
        getOverlayOrChildren().forEach(GuiComponent::handleMouseInput);
    }

    @Override
    public void handleKeyboardInput() {
        super.handleKeyboardInput();
        getOverlayOrChildren().forEach(GuiComponent::handleKeyboardInput);
    }

    /**
     * Gets the number of components in this panel.
     *
     * @return The count
     */
    public int getComponentCount() {
        return this.components.size();
    }

    /**
     * Adds a component to this panel.
     *
     * @param guiComponent The component
     */
    public void addComponent(GuiComponent guiComponent) {
        addComponent(guiComponent, (Object) null);
    }

    /**
     * Adds a component to this panel with constraints.
     *
     * @param guiComponent The component
     * @param constraints  The constraints
     */
    public void addComponent(GuiComponent guiComponent, Object constraints) {
        if (guiComponent != null) {
                guiComponent.setParent(this);
                components.add(guiComponent);
                getLayout().ifPresent(layout -> layout.addComponent(guiComponent, constraints));
        }
    }

    /**
     * Removes all components from this panel.
     */
    public void clearComponents() {
            components.forEach(comp -> {
                comp.setParent(null);
                getLayout().ifPresent(layout -> layout.removeComponent(comp));
            });
            components.clear();
            setOverlay((GuiComponent) null);
    }

    /**
     * Removes a component from this panel.
     *
     * @param guiComp The component to remove
     */
    public void removeComponent(GuiComponent guiComp) {
            components.remove(guiComp);
            getLayout().ifPresent(layout -> layout.removeComponent(guiComp));
    }

    /**
     * Sets the layout for this panel.
     *
     * @param lmg The layout manager
     */
    public void setLayout(ILayout lmg) {
        this.layout = lmg;
    }

    /**
     * Gets the layout for this panel.
     *
     * @return The layout
     */
    public Optional<ILayout> getLayout() {
        return Optional.ofNullable(layout);
    }

    /**
     * Sets the overlay for this component. An overlay temporarily replaces the
     * contents of the panel with itself. The contents are placed back when the
     * overlay is set to null.
     *
     * @param gui The component to overlay
     */
    public void setOverlay(@Nullable GuiComponent gui) {
        if (gui != null) {
            gui.setParent(this);
            gui.setLocation(gui.getLocation().copy()
                    .setWidth(getLocation().getWidth())
                    .setHeight(getLocation().getHeight()));
        } else {
            this.unfocusAll();
        }
        this.overlay = gui;
    }

    public Optional<GuiComponent> getOverlay() {
        return Optional.ofNullable(overlay);
    }

    private List<GuiComponent> getOverlayOrChildren() {
        return getOverlay().map(Arrays::asList).orElse(ImmutableList.copyOf(this.components));
    }

    /**
     * Unfocuses all components in this panel that are focusable.
     */
    public void unfocusAll() {
        for (GuiComponent comp : components) {
            if (comp.isFocusable()) {
                comp.setFocused(false);
            } else if (comp instanceof GuiPanel) {
                ((GuiPanel) comp).unfocusAll();
            }
        }
    }

    @Override
    public void onClosed() {
        this.components.forEach(GuiComponent::onClosed);
        this.getOverlay().ifPresent(GuiComponent::onClosed);
    }

    @Nonnull
    @Override
    public Dimension getMinimumSize() {
        Dimension size = getLayout().map(ILayout::getLayoutSize).orElseGet(() -> {
            int width = 0;
            int height = 0;
            for (GuiComponent gc : components) {
                width = Math.max(width, gc.getLocation().getXPos() + gc.getLocation().getWidth());
                height = Math.max(height, gc.getLocation().getYPos() + gc.getLocation().getHeight());
            }
            return new Dimension(width, height);
        });

        return size;

    }

    @Deprecated
    @Override
    public Iterator<GuiComponent> iterator() {
        return components.iterator();
    }

}
src/main/java/mnm/mods/tabbychat/api/Channel.java
public interface Channel {

    /**
     * Gets the name of this channel.
     *
     * @return The name
     */
    String getName();

    /**
     * Gets whether this channel is used for a personal message.
     *
     * @return PM status
     */
    boolean isPm();

    /**
     * Gets the alias that is displayed on the channel tab.
     *
     * @return The alias
     */
    String getAlias();

    /**
     * Sets the alias that is displayed on the channel tab.
     *
     * @param alias The new alias
     */
    void setAlias(String alias);

    /**
     * Gets the prefix that is inserted before the input.
     *
     * @return The prefix
     */
    String getPrefix();

    /**
     * Sets the prefix that is inserted before the input.
     *
     * @param prefix The new prefix
     */
    void setPrefix(String prefix);

    /**
     * Gets whether the prefix is hidden or not. A hidden prefix will not be
     * displayed while typing. Defaults to false.
     *
     * @return True if the prefix is hidden
     */
    boolean isPrefixHidden();

    /**
     * Sets whether the prefix is hidden or not. A hidden prefix will not be
     * displayed while typing.
     *
     * @param hidden Whether to hide the prefix
     */
    void setPrefixHidden(boolean hidden);

    String getCommand();

    void setCommand(String cmd);

    /**
     * Gets the channel's current status. A null indicates that there is no
     * status.
     *
     * @return The status or null
     */
    @Nullable
    ChannelStatus getStatus();

    /**
     * Sets the channel's current status.
     *
     * @param status The new status
     */
    void setStatus(@Nullable ChannelStatus status);

    /**
     * Opens the settings panel for this channel.
     */
    void openSettings();

    /**
     * Gets a list of {@link Message}s in this {@link Channel}.
     *
     * @return A list of messages
     */
    List<Message> getMessages();

    /**
     * Adds a message to the channel.
     *
     * @param text The message
     */
    void addMessage(ITextComponent text);

    /**
     * Adds a message to the channel with optional deletion. Any messages
     * previously added with the same id will be removed.
     *
     * @param text The message to send
     * @param id The id of the message
     */
    void addMessage(ITextComponent text, int id);

    /**
     * Removes messages at the given position.
     *
     * @param pos The position
     */
    void removeMessageAt(int pos);

    /**
     * Removes all messages with the given id.
     *
     * @param id The id
     */
    void removeMessages(int id);

    /**
     * Clears this channel of all message.
     */
    void clear();

}
src/main/java/mnm/mods/util/gui/events/ActionPerformedEvent.java
public class ActionPerformedEvent extends GuiEvent {

    public ActionPerformedEvent(GuiComponent component) {
        super(component);
    }
}
src/main/java/mnm/mods/tabbychat/TabbyChat.java
public class TabbyChat extends TabbyAPI {

    private static final Logger LOGGER = LogManager.getLogger(TabbyRef.MOD_ID);

    private ChatManager chatManager;
    private GuiNewChatTC chatGui;
    private EventBus bus = new EventBus();
    private Spellcheck spellcheck;

    public TabbySettings settings;
    public ServerSettings serverSettings;

    private File dataFolder;
    private InetSocketAddress currentServer;

    private boolean updateChecked;

    public TabbyChat(File configPath) {
        super();
        this.dataFolder = new File(configPath, TabbyRef.MOD_ID);
    }

    public static TabbyChat getInstance() {
        return (TabbyChat) getAPI();
    }

    public static Logger getLogger() {
        return LOGGER;
    }

    @Override
    public VersionData getVersionData() {
        return new Version();
    }

    @Override
    public ChatManager getChat() {
        return chatManager;
    }

    public GuiNewChatTC getChatGui() {
        return chatGui;
    }

    @Override
    public EventBus getBus() {
        return bus;
    }

    public Spellcheck getSpellcheck() {
        return spellcheck;
    }

    void openSettings(SettingPanel<?> setting) {
        GuiSettingsScreen screen = new GuiSettingsScreen(setting);
        Minecraft.getMinecraft().displayGuiScreen(screen);
    }

    public InetSocketAddress getCurrentServer() {
        return this.currentServer;
    }

    public File getDataFolder() {
        return dataFolder;
    }

    public void init() {

        // Set global settings
        settings = new TabbySettings();
        LiteLoader.getInstance().registerExposable(settings, null);

        spellcheck = new Spellcheck(getDataFolder());

        // Keeps the current language updated whenever it is changed.
        IReloadableResourceManager irrm = (IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager();
        irrm.registerReloadListener(spellcheck);

        bus.register(new ChatAddonAntiSpam());
        bus.register(new FilterAddon());
        bus.register(new ChatLogging(new File("logs/chat")));

    }

    public void postInit(MnmUtils utils) {
        // gui related stuff should be done here
        chatManager = new ChatManager(this);
        // this is set here because status relies on `chatManager`.
        ChatChannel.DEFAULT_CHANNEL.setStatus(ChannelStatus.ACTIVE);
        chatGui = new GuiNewChatTC(Minecraft.getMinecraft(), chatManager);

        utils.setChatProxy(new TabbedChatProxy());
    }

    public void onJoin(@Nullable SocketAddress address) {
        if (address instanceof InetSocketAddress) {
            this.currentServer = (InetSocketAddress) address;
        } else {
            this.currentServer = null;
        }

        // Set server settings
        serverSettings = new ServerSettings(currentServer);
        LiteLoader.getInstance().registerExposable(serverSettings, null);

        try {
            hookIntoChat(Minecraft.getMinecraft().ingameGUI);
        } catch (Exception e) {
            LOGGER.fatal("Unable to hook into chat.  This is bad.", e);
        }
        // load chat
        File conf = serverSettings.getFile().getParentFile();
        try {
            chatManager.loadFrom(conf);
        } catch (Exception e) {
            LOGGER.warn("Unable to load chat data.", e);
        }

        if (this.settings.general.checkUpdates.get() && !updateChecked) {
            UpdateChecker.runUpdateCheck(TabbedChatProxy.INSTANCE, TabbyRef.getVersionData());
            updateChecked = true;
        }
    }

    @SuppressWarnings("MixinClassReference")
    private void hookIntoChat(GuiIngame guiIngame) throws Exception {
        if (!GuiNewChatTC.class.isAssignableFrom(guiIngame.getChatGUI().getClass())) {
            ((IGuiIngame) guiIngame).setPersistantChatGUI(chatGui);
            LOGGER.info("Successfully hooked into chat.");
        }
    }

    private class Version implements VersionData {
        @Override
        public double getRevision() {
            return TabbyRef.MOD_REVISION;
        }

        @Override
        public String getVersion() {
            return TabbyRef.MOD_VERSION;
        }
    }
}
src/main/java/mnm/mods/util/gui/BorderLayout.java
public class BorderLayout implements ILayout {

    private EnumMap<Position, GuiComponent> components = Maps.newEnumMap(Position.class);

    @Override
    public void addComponent(GuiComponent comp, Object constraints) {
        if (constraints == null || constraints instanceof Position) {
            addComponent((Position) constraints, comp);
        } else {
            throw new IllegalArgumentException(
                    "cannot add to layout: constraint must be a Position enum");
        }
    }

    private synchronized void addComponent(Position constraint, GuiComponent comp) {
        if (constraint == null) {
            constraint = Position.CENTER;
        }

        components.put(constraint, comp);
    }

    @Override
    public synchronized void removeComponent(GuiComponent comp) {
        components.entrySet().stream()
                .filter(c -> c.getValue().equals(comp))
                .findFirst()
                .ifPresent(c -> components.remove(c.getKey()));
    }

    @Override
    public void layoutComponents(GuiPanel parent) {
        ILocation pbounds = parent.getLocation();
        GuiComponent center = components.get(Position.CENTER);
        GuiComponent north = components.get(Position.NORTH);
        GuiComponent south = components.get(Position.SOUTH);
        GuiComponent west = components.get(Position.WEST);
        GuiComponent east = components.get(Position.EAST);

        if (north != null) {
            north.setLocation(new Location(0, 0, pbounds.getWidth(), north.getMinimumSize().height));
        }

        if (west != null) {
            Location bounds = new Location();
            bounds.setWidth(west.getMinimumSize().width);

            if (north != null) {
                bounds.setYPos(north.getLocation().getHeight());
            }

            if (south == null) {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight());
                } else {
                    bounds.setHeight(pbounds.getHeight() - north.getLocation().getHeight());
                }
            } else {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight() - south.getLocation().getHeight());
                } else {
                    bounds.setHeight(pbounds.getHeight() - south.getLocation().getHeight() - north.getLocation().getHeight());
                }
            }
            west.setLocation(bounds);
        }

        if (center != null) {
            Location bounds = new Location();

            if (north != null) {
                bounds.setYPos(north.getLocation().getHeight() + 1);
            }

            if (west != null) {
                bounds.setXPos(west.getLocation().getWidth());
            }

            if (east == null) {
                if (west == null) {
                    bounds.setWidth(pbounds.getWidth());
                } else {
                    bounds.setWidth(pbounds.getWidth() - west.getLocation().getWidth());
                }
            } else {
                if (west == null) {
                    bounds.setWidth(pbounds.getWidth() - east.getLocation().getWidth());
                } else {
                    bounds.setWidth(pbounds.getWidth() - east.getLocation().getWidth() - west.getLocation().getWidth());
                }
            }

            if (south == null) {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight());
                } else {
                    bounds.setHeight(pbounds.getHeight() - north.getLocation().getHeight());
                }
            } else {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight() - south.getLocation().getHeight() - 1);
                } else {
                    bounds.setHeight(pbounds.getHeight() - south.getLocation().getHeight() - north.getLocation().getHeight() - 2);
                }
            }
            center.setLocation(bounds);
        }

        if (east != null) {
            Location bounds = new Location();

            bounds.setXPos(pbounds.getWidth() - east.getMinimumSize().width);
            bounds.setWidth(east.getMinimumSize().width);
            if (north != null) {
                bounds.setYPos(north.getLocation().getHeight());
            }
            if (south == null) {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight());
                } else {
                    bounds.setHeight(pbounds.getHeight() - north.getMinimumSize().height);
                }
            } else {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight() - south.getMinimumSize().height);
                } else {
                    bounds.setHeight(pbounds.getHeight() - south.getMinimumSize().height - north.getMinimumSize().height);
                }
            }
            east.setLocation(bounds);
        }

        if (south != null) {

            int x = 0;
            int y = pbounds.getHeight() - south.getLocation().getHeight();
            int width = pbounds.getWidth();
            int height = south.getMinimumSize().height;

            south.setLocation(new Location(x, y, width, height));
        }
    }

    @Override
    public Dimension getLayoutSize() {
        int width = 0;
        int height = 0;
        for (Entry<Position, GuiComponent> comp : components.entrySet()) {
            Dimension size = comp.getValue().getMinimumSize();
            switch (comp.getKey()) {
            case CENTER:
                width += size.width;
                height += size.height;
                break;
            case EAST:
            case WEST:
                width += size.width;
                break;
            case NORTH:
            case SOUTH:
                height += size.height;
                break;
            }
        }
        return new Dimension(width, height);
    }

    public enum Position {
        NORTH,
        SOUTH,
        EAST,
        WEST,
        CENTER
    }
}
src/main/java/mnm/mods/tabbychat/core/GuiNewChatTC.java
public class GuiNewChatTC extends GuiNewChat implements ChatScreen {

    private static GuiNewChatTC instance;

    private Minecraft mc;

    private TabbyChat tc = TabbyChat.getInstance();
    private ChatManager chat;
    private EventBus bus;

    private int prevScreenWidth;
    private int prevScreenHeight;

    public GuiNewChatTC(Minecraft minecraft, ChatManager manager) {
        super(minecraft);
        mc = minecraft;
        chat = manager;
        bus = new EventBus();
        instance = this;

        this.prevScreenHeight = mc.displayHeight;
    }

    public static GuiNewChat getInstance() {
        return instance;
    }

    @Override
    public void refreshChat() {
        chat.getChatBox().updateComponent();
    }

    @Override
    public void clearChatMessages(boolean sent) {
        checkThread(() -> {
            chat.clearMessages();
            if (sent) {
                this.getSentMessages().clear();
            }
        }).run();
    }

    @Override
    public void drawChat(int i) {
        if (prevScreenHeight != mc.displayHeight || prevScreenWidth != mc.displayWidth) {

            chat.getChatBox().onScreenHeightResize(prevScreenWidth, prevScreenHeight, mc.displayWidth, mc.displayHeight);

            prevScreenWidth = mc.displayWidth;
            prevScreenHeight = mc.displayHeight;
        }

//        if (getChatOpen())
//            return;

        ChatBox chatbox = chat.getChatBox();
        float scale = chatbox.getScale();

        GlStateManager.popMatrix(); // ignore what GuiIngame did.
        GlStateManager.pushMatrix();

        // Scale it accordingly
        GlStateManager.scale(scale, scale, 1.0F);

        // Make the upper left corner of the panel (0,0).
        GlStateManager.translate(chatbox.getBounds().x, chatbox.getBounds().y, 0.0F);

        int mouseX = Mouse.getEventX();
        int mouseY = -Mouse.getEventY() - 1;
        chatbox.drawComponent(mouseX, mouseY);

        GlStateManager.popMatrix();
        GlStateManager.pushMatrix(); // push to avoid gl errors
    }

    @Override
    public void printChatMessageWithOptionalDeletion(ITextComponent ichat, int id) {
        checkThread(() -> this.addMessage(ichat, id)).run();
    }

    public void addMessage(ITextComponent ichat, int id) {
        // chat listeners
        ChatReceivedEvent chatevent = new ChatReceivedEvent(ichat, id);
        chatevent.channels.add(ChatChannel.DEFAULT_CHANNEL);
        tc.getBus().post(chatevent);
        // chat filters
        ichat = chatevent.text;
        id = chatevent.id;
        if (ichat != null && !ichat.getUnformattedText().isEmpty()) {
            if (id != 0) {
                // send removable msg to current channel
                chatevent.channels.clear();
                chatevent.channels.add(this.chat.getActiveChannel());
            }
            if (chatevent.channels.contains(ChatChannel.DEFAULT_CHANNEL) && chatevent.channels.size() > 1
                    && !tc.serverSettings.general.useDefaultTab.get()) {
                chatevent.channels.remove(ChatChannel.DEFAULT_CHANNEL);
            }
            boolean msg = !chatevent.channels.contains(this.chat.getActiveChannel());
            final Set<String> ignored = Sets.newHashSet(this.tc.serverSettings.general.ignoredChannels.get());
            Set<Channel> channels = chatevent.channels.stream()
                    .filter(it -> !ignored.contains(it.getName()))
                    .collect(Collectors.toSet());
            for (Channel channel : channels) {
                channel.addMessage(ichat, id);
                if (msg) {
                    channel.setStatus(ChannelStatus.UNREAD);
                }
            }
            TabbyChat.getLogger().info("[CHAT] " + ichat.getUnformattedText());
            this.chat.getChatBox().updateComponent();
        }
    }

    @Override
    public void deleteChatLine(int id) {
        checkThread(() -> chat.removeMessages(id)).run();
    }

    private Runnable checkThread(Runnable runnable) {
        if (!mc.isCallingFromMinecraftThread()) {
            mc.addScheduledTask(runnable);
            TabbyChat.getLogger().warn("Tried to modify chat from thread {}. To prevent a crash, it has been scheduled on the main thread.", Thread.currentThread().getName(), new Exception());
            return Runnables.doNothing();
        }
        return runnable;
    }

    @Override
    public void resetScroll() {
        chat.getChatBox().getChatArea().resetScroll();
        super.resetScroll();
    }

    @Nonnull
    @Override
    public List<String> getSentMessages() {
        return super.getSentMessages();
    }

    public ChatManager getChatManager() {
        return chat;
    }

    @Override
    public ITextComponent getChatComponent(int clickX, int clickY) {
        return chat.getChatBox().getChatArea().getChatComponent(clickX, clickY);
    }

    @Override
    public int getChatHeight() {
        return chat.getChatBox().getChatArea().getBounds().height;
    }

    @Override
    public int getChatWidth() {
        return chat.getChatBox().getChatArea().getBounds().width;
    }

    @Override
    public EventBus getBus() {
        return bus;
    }

}
src/main/java/mnm/mods/util/gui/ILayout.java
public interface ILayout {

    /**
     * Tells this layout that a component was added.
     *
     * @param comp The component
     * @param constraints The optional constraints, may be null
     */
    void addComponent(GuiComponent comp, Object constraints);

    /**
     * Tells this layout that a component was removed.
     *
     * @param comp The component
     */
    void removeComponent(GuiComponent comp);

    /**
     * Called to layout the components. Called before the panel is drawn.
     */
    void layoutComponents(GuiPanel parent);

    /**
     * Gets the estimated size of this layout.
     *
     * @return The size.
     */
    Dimension getLayoutSize();

}
src/main/java/mnm/mods/tabbychat/api/ChannelStatus.java
public enum ChannelStatus {

    /**
     * For the active channel.
     */
    ACTIVE,
    /**
     * For when your name is mentioned.
     */
    PINGED,
    /**
     * For unread messages.
     */
    UNREAD,
    /**
     * For when players join or leave the channel.
     */
    JOINED
}
src/main/java/mnm/mods/tabbychat/api/gui/IGui.java
public interface IGui {

    /**
     * Gets the bounds of this gui relative to the parent.
     *
     * @return The size and position
     */
    Rectangle getBounds();

    /**
     * Gets the scale of this gui.
     *
     * @return The scale
     */
    float getScale();
}


Question: package mnm.mods.tabbychat.gui;
import com.google.common.collect.Maps;
import com.google.common.eventbus.Subscribe;
import mnm.mods.tabbychat.ChatChannel;
import mnm.mods.tabbychat.TabbyChat;
import mnm.mods.tabbychat.api.Channel;
import mnm.mods.tabbychat.api.ChannelStatus;
import mnm.mods.tabbychat.api.gui.IGui;
import mnm.mods.tabbychat.core.GuiNewChatTC;
import mnm.mods.util.Color;
import mnm.mods.util.TexturedModal;
import mnm.mods.util.config.Value;
import mnm.mods.util.gui.BorderLayout;
import mnm.mods.util.gui.FlowLayout;
import mnm.mods.util.gui.GuiComponent;
import mnm.mods.util.gui.GuiPanel;
import mnm.mods.util.gui.ILayout;
import mnm.mods.util.gui.events.ActionPerformedEvent;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.renderer.GlStateManager;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.Map;
import javax.annotation.Nonnull;



public class ChatTray extends GuiPanel implements IGui {

    private final static TexturedModal MODAL = new TexturedModal(ChatBox.GUI_LOCATION, 0, 14, 254, 202);

Answer:     private GuiPanel tabList = new GuiPanel(new FlowLayout());