cn.liutils.api.gui.LIGui.java Source code

Java tutorial

Introduction

Here is the source code for cn.liutils.api.gui.LIGui.java

Source

/**
 * Copyright (c) Lambda Innovation, 2013-2015
 * ??Lambda Innovation
 * http://www.li-dev.cn/
 *
 * This project is open-source, and it is distributed under 
 * the terms of GNU General Public License. You can modify
 * and distribute freely as long as you follow the license.
 * ??GNU???
 * ????
 * http://www.gnu.org/licenses/gpl.html
 */
package cn.liutils.api.gui;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.minecraft.client.Minecraft;
import org.lwjgl.opengl.GL11;

import cn.liutils.api.gui.Widget.AlignStyle;
import cn.liutils.api.key.IKeyHandler;
import cn.liutils.api.key.LIKeyProcess;
import cn.liutils.api.key.LIKeyProcess.Trigger;
import cn.liutils.core.event.eventhandler.LIFMLGameEventDispatcher;

/**
 * @author WeathFolD
 *
 */
public class LIGui implements Iterable<LIGui.WidgetNode> {

    double width, height; //Only useful when calculating 'CENTER' align preference

    private List<WidgetNode> widgets = new LinkedList();

    //If we are currently going through widgets list.
    boolean iterating = false;
    //Nodes that were added last frame during iteration to be added at next frame's beginning.
    private List<WidgetNode> nodesToAdd = new LinkedList();
    //Counter for assigning ZOrder.
    private Map<Integer, Integer> zOrderProg = new HashMap();

    public double mouseX, mouseY;

    LIKeyProcess keyProcess;
    LIKeyProcess.Trigger trigger;

    WidgetNode focus; //last input focus

    public LIGui() {
    }

    public LIGui(double width, double height) {
        this.width = width;
        this.height = height;
    }

    protected void clear() {
        dispose();
        widgets.clear();
        iterating = false;
        nodesToAdd.clear();
        zOrderProg.clear();
        focus = null;
    }

    /**
     * Called when screen is being resized.
     * @param w new width
     * @param h new height
     */
    public void resize(double w, double h) {
        boolean diff = width != w || height != h;

        this.width = w;
        this.height = h;

        if (diff) {
            for (WidgetNode node : widgets) {
                if (node.widget.alignStyle == AlignStyle.CENTER)
                    updateNode(node);
            }
        }
    }

    //---Widget handling
    /**
     * Add the constructed Widget into the LIGui.
     * @param widget
     */
    public void addWidget(Widget widget) {
        if (widget.gui != null && widget.gui != this) {
            throw new RuntimeException("Fatal: Trying to add widget " + widget + " into multiple GUIs");
        }
        widget.gui = this;
        initWidget(widget);

        if (!iterating) {
            widgets.add(widget.node);
            Collections.sort(widgets);
        } else
            nodesToAdd.add(widget.node);
        widget.onAdded();
    }

    public void addSubWidget(Widget parent, Widget sub) {
        if (sub.getWidgetParent() != null && sub.getWidgetParent() != parent) {
            throw new RuntimeException("Fatal: Trying to add widget " + sub + " into multiple GUIs");
        }
        sub.gui = this;
        sub.lastParent = parent;
        initWidget(sub);

        if (!iterating) {
            parent.node.addSubNode(sub.node); //.....
            Collections.sort(parent.node.sub);
        } else
            parent.node.toAdd.add(sub.node);
        sub.onAdded();
    }

    public void clearSubWidgets(Widget w) {
        for (WidgetNode node : w.node.sub) {
            node.widget.disposed = true;
        }
    }

    public boolean isVisible(Widget wig) {
        do {
            if (!wig.doesDraw)
                return false;
        } while ((wig = wig.getWidgetParent()) != null);
        return true;
    }

    //---Events
    /**
     * Go down the hierarchy tree and draw each widget node.
     */
    public void draw(double mx, double my) {
        frameUpdate();
        updateMouse(mx, my);
        iterating = true;
        GL11.glDisable(GL11.GL_ALPHA_TEST);
        GL11.glDepthFunc(GL11.GL_ALWAYS);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        GL11.glDepthMask(false);
        drawTraverse(mx, my, null, this, getTopNode(mx, my));
        GL11.glDepthFunc(GL11.GL_LEQUAL);
        GL11.glEnable(GL11.GL_ALPHA_TEST);
        iterating = false;
    }

    public void dispose() {
        if (trigger != null)
            trigger.setDead();
    }

    static final long DRAG_TIME_TOLE = 100;
    long lastStartTime, lastDragTime;
    WidgetNode draggingNode;
    double xOffset, yOffset;

    /**
     * Standard GUI class callback.
     * @param mx
     * @param my
     * @param btn the mouse button ID.
     * @param dt how long is this button being pressed(ms)
     */
    public void mouseClickMove(int mx, int my, int btn, long dt) {
        updateMouse(mx, my);
        if (btn == 0) {
            long time = Minecraft.getSystemTime();
            if (Math.abs(time - dt - lastStartTime) > DRAG_TIME_TOLE) {
                lastStartTime = time;
                draggingNode = getTopNode(mx, my);
                //System.out.println(draggingNode);
                if (draggingNode == null)
                    return;
                xOffset = mx - draggingNode.x;
                yOffset = my - draggingNode.y;
            }
            if (draggingNode != null) {
                lastDragTime = time;
                draggingNode.widget.onMouseDrag((mx - draggingNode.x - xOffset) / draggingNode.scale,
                        (my - draggingNode.y - yOffset) / draggingNode.scale);
            }
        }
    }

    public Widget getDraggingWidget() {
        return Math.abs(Minecraft.getSystemTime() - lastDragTime) > DRAG_TIME_TOLE || draggingNode == null ? null
                : draggingNode.widget;
    }

    /**
    * Standard GUI class callback.
    * @param mx
    * @param my
    * @param btn the mouse button ID.
    */
    public void mouseClicked(int mx, int my, int bid) {
        updateMouse(mx, my);
        if (bid == 0) {
            WidgetNode node = getTopNode(mx, my);
            if (node != null) {
                //System.out.println(node);
                if (node.widget.doesNeedFocus()) {
                    //System.out.println("focused");
                    focus = node;
                } else {
                    focus = null;
                }
                node.widget.onMouseDown((mx - node.x) / node.scale, (my - node.y) / node.scale);
            } else {
                focus = null;
            }
        }
    }

    public void keyTyped(char ch, int key) {
        if (focus != null) {
            focus.widget.handleKeyInput(ch, key);
        }
    }

    public Widget getFocus() {
        return focus == null ? null : focus.widget;
    }

    //---Key Handling
    /**
     * Add a key event listener within the lifetime of the GUI.
     */
    public void addKeyHandler(String name, int keyCode, boolean isRep, IKeyHandler ikh) {
        if (keyProcess == null) { //lazy loading
            keyProcess = new LIKeyProcess();
            keyProcess.mouseOverride = false;
            trigger = new Trigger(keyProcess);
            LIFMLGameEventDispatcher.INSTANCE.registerClientTick(trigger);
        }
        keyProcess.addKey(name, keyCode, isRep, ikh);
    }

    //---Internal Processing
    /**
     * Recalculate node's absolute position and offset, provided that its parent's data is correct.
     */
    void updateNode(WidgetNode node) {
        if (node.widget.isWidgetParent()) {
            WidgetNode parent = node.parent();
            node.scale = parent.scale * node.widget.scale;
            node.x = parent.x + node.widget.posX * node.scale;
            node.y = parent.y + node.widget.posY * node.scale;
        } else {
            node.scale = node.widget.scale;

            double x0 = 0, y0 = 0;
            switch (node.widget.alignStyle) {
            case CENTER:
                x0 = (width - node.widget.width * node.scale) / 2;
                y0 = (height - node.widget.height * node.scale) / 2;
                break;
            case LEFT:
                x0 = node.widget.posX * node.scale;
                y0 = node.widget.posY * node.scale;
                break;
            }
            node.x = Math.max(0, x0);
            node.y = Math.max(0, y0);
        }

        for (WidgetNode wn : node) {
            updateNode(wn);
        }
    }

    void initWidget(Widget widget) {
        if (widget.node == null) {
            widget.node = new WidgetNode(widget);
            updateOrder(widget);
        }
        updateNode(widget.node);
    }

    /**
     * Generic checking.
     */
    protected void frameUpdate() {
        if (!this.nodesToAdd.isEmpty()) {
            widgets.addAll(nodesToAdd);
            nodesToAdd.clear();
            Collections.sort(widgets);
        }
        updateTraverse(null, this);
    }

    private void updateTraverse(WidgetNode cur, Iterable<WidgetNode> set) {
        if (cur != null) {
            if (cur.toAdd != null && !cur.toAdd.isEmpty()) {
                cur.sub.addAll(cur.toAdd);
                cur.toAdd.clear();
                Collections.sort(cur.sub);
            }
        }
        Iterator<WidgetNode> iter = set.iterator();
        while (iter.hasNext()) {
            WidgetNode node = iter.next();
            if (node.widget.disposed) {
                iter.remove();
            } else {
                updateTraverse(node, node);
            }
        }
    }

    private void updateOrder(Widget widget) {
        //Assign z order
        int prio = widget.getDrawPriority();
        Integer prog = zOrderProg.get(prio);
        if (prog == null)
            prog = 0;
        widget.node.zOrder = prio * 100 + prog;
        zOrderProg.put(prio, (prog + 1) % 100);
    }

    private void updateMouse(double mx, double my) {
        this.mouseX = mx;
        this.mouseY = my;
    }

    public WidgetNode getTopNode(double x, double y) {
        return gtnTraverse(x, y, null, this);
    }

    private void drawTraverse(double mx, double my, WidgetNode cur, Iterable<WidgetNode> set, WidgetNode top) {
        if (cur != null && cur.widget.doesDraw) {
            GL11.glPushMatrix();
            GL11.glTranslated(cur.x, cur.y, 0);
            GL11.glScaled(cur.scale, cur.scale, 1);
            //System.out.println(cur.widget + " " + DebugUtils.formatArray(cur.x, cur.y, cur.scale));
            GL11.glColor4d(1, 1, 1, 1); //Force restore color for any widget
            cur.widget.draw((mx - cur.x) / cur.scale, (my - cur.y) / cur.scale, cur == top);
            GL11.glPopMatrix();
        }

        if (cur == null || cur.widget.doesDraw) {
            if (cur != null)
                cur.iterating = true;
            Iterator<WidgetNode> iter = set.iterator();
            while (iter.hasNext()) {
                WidgetNode wn = iter.next();
                drawTraverse(mx, my, wn, wn, top);
            }
            if (cur != null)
                cur.iterating = false;
        }
    }

    private WidgetNode gtnTraverse(double x, double y, WidgetNode node, Iterable<WidgetNode> set) {
        WidgetNode res = null;
        boolean sub = node == null || (node.widget.doesDraw && node.widget.doesListenKey);
        if (sub && node != null && node.pointWithin(x, y)) {
            res = node;
        }

        if (!sub)
            return res;

        WidgetNode next = null;
        for (WidgetNode wn : set) {
            WidgetNode tmp = gtnTraverse(x, y, wn, wn);
            if (tmp != null)
                next = tmp;
        }
        return next == null ? res : next;
    }

    public class WidgetNode implements Comparable<WidgetNode>, Iterable<WidgetNode> {
        public final Widget widget;

        public double x, y;
        public double scale;
        public int zOrder;

        private List<WidgetNode> sub = new LinkedList();

        //Design same to LIGui main class
        boolean iterating = false;
        List<WidgetNode> toAdd = new LinkedList();

        public WidgetNode(Widget wig) {
            widget = wig;
        }

        public boolean hasChild() {
            return sub != null && !sub.isEmpty();
        }

        public void addSubNode(WidgetNode wn) {
            sub.add(wn);
        }

        public List<WidgetNode> getSubNodes() {
            return sub;
        }

        public WidgetNode parent() {
            Widget wx = widget.getWidgetParent();
            return wx == null ? null : wx.node;
        }

        public boolean pointWithin(double mx, double my) {
            double x1 = x + widget.width * scale, y1 = y + widget.height * scale;
            return (x <= mx && mx <= x1) && (y <= my && my <= y1);
        }

        @Override
        public int compareTo(WidgetNode other) {
            return zOrder - other.zOrder;
        }

        @Override
        public Iterator<WidgetNode> iterator() {
            return sub.iterator();
        }

    }

    @Override
    public Iterator<WidgetNode> iterator() {
        return widgets.iterator();
    }
}