cn.lambdalib.cgui.gui.component.TextBox.java Source code

Java tutorial

Introduction

Here is the source code for cn.lambdalib.cgui.gui.component.TextBox.java

Source

/**
* Copyright (c) Lambda Innovation, 2013-2016
* This file is part of LambdaLib modding library.
* https://github.com/LambdaInnovation/LambdaLib
* Licensed under MIT, see project root for more information.
*/
package cn.lambdalib.cgui.gui.component;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

import cn.lambdalib.cgui.gui.*;
import cn.lambdalib.core.LambdaLib;
import cn.lambdalib.s11n.SerializeIncluded;
import cn.lambdalib.util.client.font.Fonts;
import cn.lambdalib.util.client.font.IFont;
import cn.lambdalib.util.client.font.IFont.FontOption;
import cn.lambdalib.util.client.font.TrueTypeFont;
import cn.lambdalib.util.generic.MathUtils;
import org.apache.commons.lang3.StringUtils;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11;

import cn.lambdalib.cgui.gui.annotations.CopyIgnore;
import cn.lambdalib.cgui.gui.component.Transform.HeightAlign;
import cn.lambdalib.cgui.gui.event.FrameEvent;
import cn.lambdalib.cgui.gui.event.GuiEvent;
import cn.lambdalib.cgui.gui.event.KeyEvent;
import cn.lambdalib.cgui.gui.event.LeftClickEvent;
import cn.lambdalib.util.helper.Color;
import cn.lambdalib.util.helper.GameTimer;
import net.minecraft.util.ChatAllowedCharacters;
import net.minecraft.util.StatCollector;

import javax.vecmath.Vector2d;

/**
 * Textbox displays text on the widget area, it might also be edited. TextBox is designed to handle single-line texts.
 * @author WeAthFolD
 */
public class TextBox extends Component {

    /**
     * Fired each time the TextBox's content is being edited.
     */
    public static class ChangeContentEvent implements GuiEvent {
    }

    /**
     * Fired each time the TextBox's input is confirmed. (a.k.a. User presses enter)
     */
    public static class ConfirmInputEvent implements GuiEvent {
    }

    public String content = "";

    @SerializeIncluded
    public IFont font = Fonts.getDefault();

    public FontOption option;

    public HeightAlign heightAlign = HeightAlign.CENTER;

    /**
     * Only activated when doesn't allow edit. If activated, The display string will be
     *  <code>StatCollector.translateToLocal(content).</code>
     */
    public boolean localized = false;

    /**
     * Whether the editing is enabled.
     */
    public boolean allowEdit = false;

    /**
     * Whether this textBox doesn't draw chars that are out of bounds.
     */
    public boolean emit = true;

    public boolean doesEcho = false;
    public char echoChar = '*';

    public double zLevel = 0;

    public double xOffset, yOffset;

    private int caretPos = 0;

    private int displayOffset = 0;

    public TextBox() {
        this(new FontOption());
    }

    public TextBox(FontOption _option) {
        super("TextBox");
        this.option = _option;
    }

    {
        // Draws the content
        listen(FrameEvent.class, (w, e) -> {
            validate();

            final Vector2d origin = origin();
            final double widthLimit = w.transform.width - xOffset;

            final String processed = processedContent().substring(displayOffset);

            final int localCaret = caretPos - displayOffset; // [0, processed.length]

            double acc = 0.0;
            int i = processed.length();
            if (emit) {
                for (i = 0; i < processed.length() && acc < widthLimit; ++i) {
                    acc += font.getCharWidth(processed.codePointAt(i), option);
                }
            }

            final String display = processed.substring(0, i);

            GL11.glPushMatrix();
            GL11.glTranslated(0, 0, zLevel);

            font.draw(display, origin.x, origin.y, option);

            if (w.isFocused() && allowEdit && GameTimer.getAbsTime() % 2000 < 1000) {
                font.draw("|", origin.x + sumLength(display, 0, localCaret), origin.y - 1, option);
            }

            GL11.glPopMatrix();
        });

        // Handles input
        listen(KeyEvent.class, (__, evt) -> {
            if (!allowEdit) {
                return;
            }

            final char input = evt.inputChar;
            final int keyCode = evt.keyCode;

            if (keyCode == Keyboard.KEY_RIGHT) {
                caretPos = Math.min(content.length(), caretPos + 1);
                checkCaretRegion();
            } else if (keyCode == Keyboard.KEY_LEFT) {
                caretPos = Math.max(0, caretPos - 1);
                if (caretPos < displayOffset) {
                    displayOffset = caretPos;
                }
            } else if (keyCode == Keyboard.KEY_V && Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) {
                setContent(content.substring(0, caretPos) + getClipboardContent() + content.substring(caretPos));
                validate();

                widget.post(new ChangeContentEvent());
            } else if (keyCode == Keyboard.KEY_C && Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) {
                saveClipboardContent();
            } else if (keyCode == Keyboard.KEY_BACK) {
                if (caretPos != 0) {
                    content = content.substring(0, caretPos - 1) + content.substring(caretPos);
                    --caretPos;
                    if (displayOffset != 0) {
                        --displayOffset;
                    }
                    widget.post(new ChangeContentEvent());

                    checkCaretRegion();
                    validate();
                }
            } else if (keyCode == Keyboard.KEY_RETURN || keyCode == Keyboard.KEY_NUMPADENTER) {
                widget.post(new ConfirmInputEvent());
            } else if (keyCode == Keyboard.KEY_DELETE) {
                content = "";
                widget.post(new ChangeContentEvent());

                validate();
            } else if (ChatAllowedCharacters.isAllowedCharacter(input)) {
                content = content.substring(0, caretPos) + input + content.substring(caretPos);
                caretPos = Math.min(content.length(), caretPos + 1);
                widget.post(new ChangeContentEvent());

                checkCaretRegion();
            }
        });

        // Mouse caret pos selection
        listen(LeftClickEvent.class, (w, evt) -> {
            if (!allowEdit) {
                return;
            }

            final Vector2d origin = origin();
            final String display = processedContent().substring(displayOffset);
            final double rel_x = origin.x - font.getTextWidth(display, option) * option.align.lenOffset + evt.x;

            double acc = 0.0;
            int ind = 0;
            for (; acc < rel_x && ind < display.length(); ++ind) {
                acc += font.getCharWidth(display.codePointAt(ind), option);
            }

            if (ind > 0 && rel_x < acc - font.getCharWidth(display.codePointAt(ind - 1), option) * 0.5) {
                ind--;
            }

            caretPos = displayOffset + ind;
            checkCaretRegion();
        });
    }

    public TextBox allowEdit() {
        allowEdit = true;
        return this;
    }

    public TextBox setContent(String str) {
        content = str;
        return this;
    }

    public TextBox setFont(IFont font) {
        this.font = font;
        return this;
    }

    private void validate() {
        if (!allowEdit) {
            displayOffset = caretPos = 0;
            return;
        }

        if (displayOffset >= content.length() || caretPos > content.length()) {
            displayOffset = caretPos = 0;
        }
    }

    private Vector2d origin() {
        return new Vector2d(widget.transform.width * option.align.lenOffset + xOffset,
                Math.max(0, widget.transform.height - option.fontSize) * heightAlign.factor + yOffset);
    }

    private boolean shouldLocalize() {
        return !allowEdit && localized;
    }

    private void checkCaretRegion() {
        final double widthLimit = widthLimit();
        final String local = processedContent().substring(displayOffset);
        final int localCaret = caretPos - displayOffset;
        final double distance = sumLength(local, 0, localCaret);
        if (distance > widthLimit) {
            double acc = 0.0;
            int mini = 0;
            for (; mini < localCaret && distance - acc > widthLimit; ++mini) {
                acc += font.getCharWidth(local.codePointAt(mini), option);
            }
            displayOffset += mini;
        }

        assert displayOffset < caretPos;
    }

    private double widthLimit() {
        return widget.transform.width - xOffset;
    }

    private String processedContent() {
        String ret = content;
        if (shouldLocalize()) {
            ret = StatCollector.translateToLocal(ret);
        }
        if (doesEcho) {
            ret = StringUtils.repeat(echoChar, ret.length());
        }

        return ret;
    }

    private double sumLength(String str, int begin, int end) {
        return font.getTextWidth(str.substring(begin, end), option);
    }

    private String getClipboardContent() {
        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
        if (cb.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
            try {
                return (String) cb.getData(DataFlavor.stringFlavor);
            } catch (UnsupportedFlavorException | IOException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    private void saveClipboardContent() {
        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
        StringSelection ss = new StringSelection(content);
        cb.setContents(ss, ss);
    }

    public static TextBox get(Widget w) {
        return w.getComponent("TextBox");
    }

}