src/main/java/mnm/mods/tabbychat/util/ChatVisibility.java
public enum ChatVisibility {

    ALWAYS,
    NORMAL,
    HIDDEN

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

    int getXPos();

    int getYPos();

    int getWidth();

    int getHeight();

    ILocation asImmutable();

    default int getXWidth() {
        return getXPos() + getWidth();
    }

    default int getYHeight() {
        return getYPos() + getHeight();
    }

    default Location copy() {
        return Location.copyOf(this);
    }

    default Point getPoint() {
        return new Point(getXPos(), getYPos());
    }

    default Dimension getSize() {
        return new Dimension(this.getWidth(), this.getHeight());
    }

    default Rectangle asRectangle() {
        return new Rectangle(getXPos(), getYPos(), getWidth(), getHeight());
    }

    default boolean contains(ILocation r) {

        return this.getXPos() < r.getXWidth()
                && this.getXWidth() > r.getXPos()
                && this.getYPos() < r.getYHeight()
                && this.getYHeight() > r.getYPos();
    }
}
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/events/GuiMouseEvent.java
public enum MouseEvent {
    RAW,
    CLICK,
    RELEASE,
    SCROLL,
    DRAG,;
}
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/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/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/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/util/gui/events/GuiMouseEvent.java
public class GuiMouseEvent extends AbstractMouseEvent {

    private MouseEvent type;
    private int button;
    private long buttonTime;
    private int scroll;

    public GuiMouseEvent(GuiComponent component, MouseEvent event, int mouseX, int mouseY, int button, long buttonTime) {
        this(component, event, mouseX, mouseY, button, buttonTime, 0);
    }

    public GuiMouseEvent(GuiComponent component, MouseEvent type, int mouseX, int mouseY, int button, long buttonTime, int scroll) {
        super(component, mouseX, mouseY);
        this.type = type;
        this.button = button;
        this.scroll = scroll;
    }

    public MouseEvent getType() {
        return type;
    }

    public int getButton() {
        return button;
    }
    
    public long getButtonTime() {
        return buttonTime;
    }

    public int getScroll() {
        return scroll;
    }

    public enum MouseEvent {
        RAW,
        CLICK,
        RELEASE,
        SCROLL,
        DRAG,;
    }
}
src/main/java/mnm/mods/util/gui/GuiButton.java
public class GuiButton extends GuiComponent {

    private static final ResourceLocation WIDGETS = new ResourceLocation("textures/gui/widgets.png");
    private static final TexturedModal MODAL_NORMAL = new TexturedModal(WIDGETS, 0, 66, 200, 20);
    private static final TexturedModal MODAL_HOVER = new TexturedModal(WIDGETS, 0, 86, 200, 20);
    private static final TexturedModal MODAL_DISABLE = new TexturedModal(WIDGETS, 0, 46, 200, 20);

    private String text = "";
    private SoundEvent sound;

    /**
     * Instantiates a new button with {@code text} as the display string.
     *
     * @param text The display string
     */
    public GuiButton(String text) {
        this.setText(text);
        setSound(SoundEvents.UI_BUTTON_CLICK);
    }

    @Subscribe
    public void onClick(ActionPerformedEvent action) {
        SoundEvent sound = getSound();
        if (sound != null) {
            mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(sound, 1.0F));
        }
    }

    /**
     * Sets the display text for this button.
     *
     * @param text The new text
     */
    public void setText(String text) {
        if (text == null) {
            text = "";
        }
        this.text = text;
    }

    /**
     * Gets the display text for this button.
     *
     * @return The text
     */
    public String getText() {
        return this.text;
    }

    public void setSound(SoundEvent sound) {
        this.sound = sound;
    }

    public SoundEvent getSound() {
        return sound;
    }

    @Override
    public void drawComponent(int mouseX, int mouseY) {
        FontRenderer fontrenderer = mc.fontRenderer;
        ILocation bounds = getLocation();

        mc.getTextureManager().bindTexture(WIDGETS);
        GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);

        TexturedModal modal = this.getHoverState(isHovered());
        GlStateManager.enableBlend();
        OpenGlHelper.glBlendFunc(770, 771, 1, 0);
        GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

        this.drawModalCorners(modal);

        int textColor = 0xE0E0E0;

        if (!this.isEnabled()) {
            textColor = 0xA0A0A0;
        } else if (this.isHovered()) {
            textColor = 0xFFFFA0;
        }

        this.drawCenteredString(fontrenderer, getText(), bounds.getWidth() / 2, (bounds.getHeight() - 8) / 2,
                textColor);

        super.drawComponent(mouseX, mouseY);
    }

    private TexturedModal getHoverState(boolean hovered) {
        TexturedModal modal = GuiButton.MODAL_NORMAL;

        if (!this.isEnabled()) {
            modal = GuiButton.MODAL_DISABLE;
        } else if (hovered) {
            modal = GuiButton.MODAL_HOVER;
        }

        return modal;
    }

    @Nonnull
    @Override
    public Dimension getMinimumSize() {
        return new Dimension(mc.fontRenderer.getStringWidth(this.getText()) + 8, 20);
    }

}
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();

}


Question: package mnm.mods.tabbychat.gui;
import com.google.common.eventbus.Subscribe;
import mnm.mods.tabbychat.TabbyChat;
import mnm.mods.tabbychat.api.Channel;
import mnm.mods.tabbychat.api.ChannelStatus;
import mnm.mods.tabbychat.core.GuiNewChatTC;
import mnm.mods.tabbychat.util.ChatVisibility;
import mnm.mods.util.Color;
import mnm.mods.util.ILocation;
import mnm.mods.util.TexturedModal;
import mnm.mods.util.gui.GuiButton;
import mnm.mods.util.gui.events.GuiMouseEvent;
import mnm.mods.util.gui.events.GuiMouseEvent.MouseEvent;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.GlStateManager;
import java.awt.Dimension;
import javax.annotation.Nonnull;



public class ChatTab extends GuiButton {

    private static final TexturedModal ACTIVE = new TexturedModal(ChatBox.GUI_LOCATION, 0, 0, 50, 14);
    private static final TexturedModal UNREAD = new TexturedModal(ChatBox.GUI_LOCATION, 50, 0, 50, 14);
    private static final TexturedModal PINGED = new TexturedModal(ChatBox.GUI_LOCATION, 100, 0, 50, 14);
    private static final TexturedModal HOVERED = new TexturedModal(ChatBox.GUI_LOCATION, 150, 0, 50, 14);
    private static final TexturedModal NONE = new TexturedModal(ChatBox.GUI_LOCATION, 200, 0, 50, 14);

    private final Channel channel;

    public ChatTab(Channel channel) {
        super(channel.getAlias());
        this.channel = channel;
    }

    @Subscribe
Answer:     public void tryCommitSudoku(GuiMouseEvent event) {