net.malisis.core.client.gui.component.UIComponent.java Source code

Java tutorial

Introduction

Here is the source code for net.malisis.core.client.gui.component.UIComponent.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 PaleoCrafter, Ordinastie
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package net.malisis.core.client.gui.component;

import java.util.LinkedHashSet;
import java.util.Set;

import net.malisis.core.client.gui.Anchor;
import net.malisis.core.client.gui.ClipArea;
import net.malisis.core.client.gui.GuiRenderer;
import net.malisis.core.client.gui.MalisisGui;
import net.malisis.core.client.gui.component.container.UIContainer;
import net.malisis.core.client.gui.component.control.IControlComponent;
import net.malisis.core.client.gui.component.decoration.UITooltip;
import net.malisis.core.client.gui.element.GuiShape;
import net.malisis.core.client.gui.element.SimpleGuiShape;
import net.malisis.core.client.gui.event.ComponentEvent;
import net.malisis.core.client.gui.event.KeyboardEvent;
import net.malisis.core.client.gui.event.MouseEvent;
import net.malisis.core.client.gui.event.component.ContentUpdateEvent;
import net.malisis.core.client.gui.event.component.SpaceChangeEvent.PositionChangeEvent;
import net.malisis.core.client.gui.event.component.SpaceChangeEvent.SizeChangeEvent;
import net.malisis.core.client.gui.event.component.StateChangeEvent.DisabledStateChange;
import net.malisis.core.client.gui.event.component.StateChangeEvent.FocusStateChange;
import net.malisis.core.client.gui.event.component.StateChangeEvent.HoveredStateChange;
import net.malisis.core.client.gui.event.component.StateChangeEvent.VisibleStateChange;
import net.malisis.core.client.gui.icon.GuiIcon;
import net.malisis.core.renderer.RenderParameters;
import net.malisis.core.renderer.animation.transformation.ITransformable;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;

import com.google.common.eventbus.EventBus;

/**
 * {@link UIComponent} is the base of everything drawn onto a GUI.<br>
 * The drawing is separated between background and foreground.<br>
 * Most of the events are launched from UIComponent.
 *
 * @author Ordinastie, PaleoCrafter
 * @param <T> the type of <code>UIComponent</code>
 */
public abstract class UIComponent<T extends UIComponent>
        implements ITransformable.Position<T>, ITransformable.Size<T>, ITransformable.Alpha {
    public final static int INHERITED = 0;

    /** Reference to the {@link MalisisGui} this {@link UIComponent} was added to. */
    private final MalisisGui gui;
    /** List of {@link UIComponent components} controling this {@link UIContainer}. */
    private final Set<IControlComponent> controlComponents;
    /** Position of this {@link UIComponent}. */
    protected int x, y;
    /** Z index of the component. */
    protected int zIndex = INHERITED;
    /** Position anchor for this {@link UIComponent}. See {@link Anchor} */
    protected int anchor = Anchor.NONE;
    /** Size of this {@link UIComponent}. */
    protected int width = INHERITED, height = INHERITED;
    /** Event bus on which event listeners are registered. */
    private EventBus bus;
    /** The parent {@link UIComponent} of this <code>UIComponent</code>. */
    protected UIComponent parent;
    /** The name of this {@link UIComponent} Can be used to retrieve this back from a container. */
    protected String name;
    /** The tooltip for this {@link UIComponent} Automatically displayed when the {@link UIComponent} is hovered. */
    protected UITooltip tooltip;
    /** Determines whether this {@link UIComponent} is visible. */
    protected boolean visible = true;
    /**
     * Determines whether this {@link UIComponent} is enabled. If set to false, will cancel any
     * {@link net.malisis.core.client.gui.event.GuiEvent events} received.
     */
    protected boolean disabled = false;
    /** Hover state of this {@link UIComponent}. */
    protected boolean hovered = false;
    /** Focus state of this {@link UIComponent}. */
    protected boolean focused = false;
    /** GuiShape used to draw this {@link UIComponent}. */
    protected GuiShape shape;
    /** {@link RenderParameters} used to draw this {@link UIComponent}. */
    protected RenderParameters rp;
    /** {@link GuiIcon} used to draw this {@link UIComponent}. */
    protected GuiIcon icon;
    /** Alpha transparency of this {@link UIComponent}. */
    protected int alpha = 255;

    /**
     * Instantiates a new {@link UIComponent}.
     *
     * @param gui the gui
     */
    public UIComponent(MalisisGui gui) {
        this.gui = gui;
        bus = new EventBus();
        bus.register(this);
        controlComponents = new LinkedHashSet<>();
        rp = new RenderParameters();
        shape = new SimpleGuiShape();
    }

    // #region getters/setters
    /**
     * Gets the {@link MalisisGui} this {@link UIComponent} was added to.
     *
     * @return the gui
     */
    public MalisisGui getGui() {
        return gui;
    }

    /**
     * Sets the position of this {@link UIComponent}.
     *
     * @param x the x
     * @param y the y
     * @return this {@link UIComponent}
     */
    @Override
    public T setPosition(int x, int y) {
        return setPosition(x, y, anchor);
    }

    /**
     * Sets the position of this {@link UIComponent} relative to an anchor.
     *
     * @param x the x
     * @param y the y
     * @param anchor the anchor
     * @return this {@link UIComponent}
     */
    public T setPosition(int x, int y, int anchor) {
        //backup values
        int oldX = this.x;
        int oldY = this.y;
        int oldAnchor = this.anchor;

        this.x = x;
        this.y = y;
        this.anchor = anchor;

        if (!fireEvent(new PositionChangeEvent(this, x, y, anchor))) {
            //event is cancelled, restore old values
            this.x = oldX;
            this.y = oldY;
            this.anchor = oldAnchor;
            return (T) this;
        }

        return (T) this;
    }

    /**
     * Gets the X coordinate of this {@link UIComponent}'s position.
     *
     * @return the coordinate
     */
    public int getX() {
        return x;
    }

    /**
     * Gets the Y coordinate of this {@link UIComponent}'s position.
     *
     * @return the coordinate
     */
    public int getY() {
        return y;
    }

    /**
     * Sets the zIndex for this {@link UIComponent}.
     *
     * @param zIndex the z index
     * @return this {@link UIComponent}
     */
    public T setZIndex(int zIndex) {
        this.zIndex = zIndex;
        return (T) this;
    }

    /**
     * Gets the zIndex of this {@link UIComponent}.
     *
     * @return the zIndex
     */
    public int getZIndex() {
        return zIndex == INHERITED ? (parent != null ? parent.getZIndex() : 0) : zIndex;
    }

    /**
     * Sets the anchor for this {@link UIComponent}'s position.
     *
     * @param anchor the anchor
     * @return this {@link UIComponent}
     */
    public T setAnchor(int anchor) {
        int oldAnchor = this.anchor;
        this.anchor = anchor;

        if (!fireEvent(new PositionChangeEvent(this, x, y, anchor))) {
            //event is cancelled, restore old values
            this.anchor = oldAnchor;
            return (T) this;
        }
        return (T) this;
    }

    /**
     * Gets the anchor.
     *
     * @return the anchor of this {@link UIComponent}'s position
     */
    public int getAnchor() {
        return anchor;
    }

    /**
     * Sets the size of this {@link UIComponent}.
     *
     * @param width the width
     * @param height the height
     * @return this {@link UIComponent}
     */
    @Override
    public T setSize(int width, int height) {
        int oldWidth = this.width;
        int oldHeight = this.height;

        this.width = width;
        this.height = height;

        if (!fireEvent(new SizeChangeEvent(this, width, height))) {
            //event is cancelled, restore old values
            this.width = oldWidth;
            this.height = oldHeight;
            return (T) this;
        }

        return (T) this;
    }

    /**
     * Gets the raw width of this {@link UIComponent}
     *
     * @return the width
     */
    public int getRawWidth() {
        return width;
    }

    /**
     * Gets the width of this {@link UIComponent}.
     *
     * @return the width, or 0 for relative width without a parent
     */
    public int getWidth() {
        if (width > 0)
            return width;

        if (parent == null)
            return 0;

        //if width < 0 consider it relative to parent container
        int w = parent.getWidth() + width;
        if (parent instanceof UIContainer)
            w -= 2 * ((UIContainer) parent).getHorizontalPadding();

        return w;
    }

    /**
     * Checks if the width of this {@link UIComponent} is relative to its parent <code>UIComponent</code>.
     *
     * @return true, if the width is relative
     */
    public boolean isRelativeWidth() {
        return width <= 0;
    }

    /**
     * Gets the raw height of this {@link UIComponent}.
     *
     * @return the height
     */
    public int getRawHeight() {
        return height;
    }

    /**
     * Gets the height of this {@link UIComponent}.
     *
     * @return the height, or 0 for relative width without a parent
     */
    public int getHeight() {
        if (height > 0)
            return height;

        if (parent == null)
            return 0;

        //if height < 0 consider it relative to parent container
        int h = parent.getHeight() + height;
        if (parent instanceof UIContainer)
            h -= 2 * ((UIContainer) parent).getVerticalPadding();

        return h;
    }

    /**
     * Checks if the height of this {@link UIComponent} is relative to its parent <code>UIComponent</code>.
     *
     * @return true, if the height is relative
     */
    public boolean isRelativeHeight() {
        return height <= 0;
    }

    /**
     * Sets the <code>hovered</code> state of this {@link UIComponent}.
     *
     * @param hovered the new state
     */
    public void setHovered(boolean hovered) {
        boolean flag = this.hovered != hovered;
        flag |= MalisisGui.setHoveredComponent(this, hovered);
        if (!flag)
            return;

        this.hovered = hovered;
        fireEvent(new HoveredStateChange(this, hovered));

        if (tooltip != null && hovered)
            tooltip.animate();
    }

    /**
     * Gets the <code>hovered</code> state of this {@link UIComponent}.
     *
     * @return true, this component is hovered
     */
    public boolean isHovered() {
        return this.hovered;
    }

    /**
     * Sets the <code>focused</code> state of this {@link UIComponent}.
     *
     * @param focused the state
     */
    public void setFocused(boolean focused) {
        if (isDisabled())
            return;

        boolean flag = this.focused != focused;
        flag |= MalisisGui.setFocusedComponent(this, focused);
        if (!flag)
            return;

        this.focused = focused;
        fireEvent(new FocusStateChange(this, focused));
    }

    /**
     * Gets the <code>focused</code> state of this {@link UIComponent}.
     *
     * @return true, if this component if focused
     */
    public boolean isFocused() {
        return this.focused;
    }

    /**
     * Gets the parent of this {@link UIComponent}.
     *
     * @return the parent
     */
    public UIComponent getParent() {
        return parent;
    }

    /**
     * Sets the parent of this {@link UIComponent}.
     *
     * @param parent the parent
     */
    public void setParent(UIComponent parent) {
        this.parent = parent;
        fireEvent(new ContentUpdateEvent(this));
    }

    /**
     * Checks if this {@link UIComponent} is visible.
     *
     * @return true, if visible
     */
    public boolean isVisible() {
        return visible;
    }

    /**
     * Sets the visibility of this {@link UIComponent}.
     *
     * @param visible the visibility for this component
     * @return this {@link UIComponent}
     */
    public T setVisible(boolean visible) {
        if (isVisible() == visible)
            return (T) this;

        if (!fireEvent(new VisibleStateChange(this, visible)))
            return (T) this;

        this.visible = visible;
        if (!visible) {
            this.setHovered(false);
            this.setFocused(false);
        }

        return (T) this;
    }

    /**
     * Checks if this {@link UIComponent} is disabled
     *
     * @return true if disabled
     */
    public boolean isDisabled() {
        return disabled || (parent != null && parent.isDisabled());
    }

    /**
     * Set the state of this {@link UIComponent}.
     *
     * @param disabled the new state
     * @return this {@link UIComponent}
     */
    public T setDisabled(boolean disabled) {
        if (isDisabled() == disabled)
            return (T) this;

        if (!fireEvent(new DisabledStateChange(this, disabled)))
            return (T) this;

        this.disabled = disabled;
        if (disabled) {
            setHovered(false);
            setFocused(false);
        }
        return (T) this;
    }

    /**
     * Gets the name of this {@link UIComponent}.
     *
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * Sets the name of this {@link UIComponent}.
     *
     * @param name the name to be used
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Gets the {@link UITooltip} for this {@link UIComponent}.
     *
     * @return the tooltip
     */
    public UITooltip getTooltip() {
        return tooltip;
    }

    /**
     * Sets the {@link UITooltip} of this {@link UIComponent}.
     *
     * @param tooltip the tooltip
     * @return this {@link UIComponent}
     */
    public T setTooltip(UITooltip tooltip) {
        this.tooltip = tooltip;
        return (T) this;
    }

    /**
     * Sets the {@link UITooltip} of this {@link UIComponent}.
     *
     * @param text the text of the tooltip
     * @return the t
     */
    public T setTooltip(String text) {
        setTooltip(new UITooltip(getGui(), text));
        return (T) this;
    }

    /**
     * Sets the alpha transparency for this {@link UIComponent}.
     *
     * @param alpha the new alpha
     */
    @Override
    public void setAlpha(int alpha) {
        this.alpha = alpha;
    }

    /**
     * Gets the alpha transparency for this {@link UIComponent}.
     *
     * @return the alpha
     */
    public int getAlpha() {
        if (getParent() == null)
            return alpha;

        return Math.min(alpha, parent.getAlpha());
    }

    // #end getters/setters

    /**
     * Registers an <code>object</code> to handle events received by this {@link UIComponent}.
     *
     * @param object object whose handler methods should be registered
     * @return this {@link UIComponent}
     */
    public T register(Object object) {
        bus.register(object);
        return (T) this;
    }

    /**
     * Unregister an <code>object</code> to stop receiving events for this {@link UIComponent}.
     *
     * @param object the object
     * @return this {@link UIComponent}
     */
    public T unregister(Object object) {
        bus.unregister(object);
        return (T) this;
    }

    /**
     * Fires a {@link ComponentEvent}.
     *
     * @param event the event
     * @return true, if the even can propagate, false if cancelled
     */
    public boolean fireEvent(ComponentEvent event) {
        bus.post(event);
        return !event.isCancelled();
    }

    /**
     * Fires a {@link MouseEvent}.
     *
     * @param event the event
     * @return true, if the even can propagate, false if cancelled
     */
    public boolean fireMouseEvent(MouseEvent event) {
        if (isDisabled() || !isVisible())
            return false;

        bus.post(event);
        return !event.isCancelled();
    }

    /**
     * Fires a {@link KeyboardEvent}.
     *
     * @param event the event
     * @return true, if the even can propagate, false if cancelled
     */
    public boolean fireKeyboardEvent(KeyboardEvent event) {
        if (isDisabled())
            return false;

        for (IControlComponent c : controlComponents)
            c.fireKeyboardEvent(event);

        bus.post(event);
        return !event.isCancelled();
    }

    /**
     * Checks if supplied coordinates are inside this {@link UIComponent} bounds.
     *
     * @param x the x
     * @param y the y
     * @return true, if coordinates are inside bounds
     */
    public boolean isInsideBounds(int x, int y) {
        if (!isVisible())
            return false;
        return x >= screenX() && x <= screenX() + getWidth() && y >= screenY() && y <= screenY() + getHeight();
    }

    /**
     * Gets the {@link UIComponent} at the specified coordinates.<br>
     * Will return a {@link IControlComponent} if any. Checks if inside bounds, visible and not disabled.
     *
     * @param x the x
     * @param y the y
     * @return this {@link UIComponent} or null if outside its bounds.
     */
    public UIComponent getComponentAt(int x, int y) {
        //control components take precedence over regular components
        for (IControlComponent c : controlComponents) {
            UIComponent component = c.getComponentAt(x, y);
            if (component != null)
                return component;
        }

        return isInsideBounds(x, y) ? this : null;
    }

    /**
     * Gets the X coordinate relative to this {@link UIComponent}.
     *
     * @param x the x
     * @return the coordinate
     */
    public int relativeX(int x) {
        return x - screenX();
    }

    /**
     * Gets the Y coordinate relative to this {@link UIComponent}.
     *
     * @param y the y
     * @return the coordinate
     */
    public int relativeY(int y) {
        return y - screenY();
    }

    /**
     * Gets the X coordinate of a {@link UIComponent} inside this <code>UIComponent</code>.
     *
     * @param component the component
     * @return the coordinate
     */
    public int componentX(UIComponent component) {
        int x = component.getX();
        int w = getWidth() - component.getWidth();
        int a = Anchor.horizontal(component.getAnchor());
        if (a == Anchor.CENTER)
            x += w / 2;
        else if (a == Anchor.RIGHT)
            x += w;
        return x;
    }

    /**
     * Gets the Y coordinate of a {@link UIComponent} inside this <code>UIComponent</code>.
     *
     * @param component the component
     * @return the coordinate
     */
    public int componentY(UIComponent component) {
        int y = component.getY();
        int h = getHeight() - component.getHeight();
        int a = Anchor.vertical(component.getAnchor());
        if (a == Anchor.MIDDLE)
            y += h / 2;
        else if (a == Anchor.BOTTOM)
            y += h;
        return y;
    }

    /**
     * Gets the X coordinate of this {@link UIComponent} relative to its parent.
     *
     * @return the coordinate
     */
    public int parentX() {
        return getParent() != null ? getParent().componentX(this) : getX();
    }

    /**
     * Get the Y coordinate of this {@link UIComponent} relative to its parent.
     *
     * @return the coordinate
     */
    public int parentY() {
        return getParent() != null ? getParent().componentY(this) : getY();
    }

    /**
     * Gets the X coordinate of this {@link UIComponent} relative to the screen.
     *
     * @return the the coordinate
     */
    public int screenX() {
        int x = parentX();
        if (getParent() != null)
            x += getParent().screenX();
        return x;
    }

    /**
     * Gets the Y coordinate of this {@link UIComponent} relative to the screen.
     *
     * @return the coordinate
     */
    public int screenY() {
        int y = parentY();
        if (getParent() != null)
            y += getParent().screenY();
        return y;
    }

    /**
     * Adds a {@link IControlComponent} component to this {@link UIComponent}.
     *
     * @param component the component
     */
    public void addControlComponent(IControlComponent component) {
        controlComponents.add(component);
        component.setParent(this);
    }

    /**
     * Removes the {@link IControlComponent} from this {@link UIComponent}.
     *
     * @param component the component
     */
    public void removeControlComponent(IControlComponent component) {
        if (component.getParent() != this)
            return;

        controlComponents.remove(component);
        component.setParent(null);
    }

    /**
     * Removes all the {@link IControlComponent} from this {@link UIContainer}.
     */
    public void removeAllControlComponents() {
        for (IControlComponent component : controlComponents)
            component.setParent(null);
        controlComponents.clear();
    }

    /**
     * Called when this {@link UIComponent} is added to screen.<br>
     * Triggers a {@link SizeChangeEvent} is this component size is relative.
     */
    public void onAddedToScreen() {
        if (width <= 0 || height <= 0)
            fireEvent(new SizeChangeEvent<UIComponent>(this, getWidth(), getHeight()));
    }

    /**
     * Draws this {@link UIComponent} Called by {@link #parent} component.<br>
     * Will set the size of {@link #shape} according to the size of this <code>UIComponent</code><br>
     * Rendering is surrounded by glPushAttrib(GL_ALL_ATTRIB_BITS) so no state should bleed between components. Also, a draw() is triggered
     * between background and foreground.
     *
     * @param renderer the renderer
     * @param mouseX the mouse x
     * @param mouseY the mouse y
     * @param partialTick the partial tick
     */
    public void draw(GuiRenderer renderer, int mouseX, int mouseY, float partialTick) {
        if (!isVisible())
            return;

        if (shape != null) {
            shape.resetState();
            shape.setSize(getWidth(), getHeight());
        }
        if (rp != null)
            rp.reset();

        GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
        if (getAlpha() < 255) {
            GL11.glBlendFunc(GL11.GL_CONSTANT_ALPHA, GL11.GL_ONE_MINUS_CONSTANT_ALPHA);
            GL14.glBlendColor(1, 1, 1, (float) getAlpha() / 255);
        }

        //draw background
        renderer.currentComponent = this;
        drawBackground(renderer, mouseX, mouseY, partialTick);
        renderer.next();

        //draw foreground
        renderer.currentComponent = this;

        ClipArea area = this instanceof IClipable ? ((IClipable) this).getClipArea() : null;
        if (area != null)
            renderer.startClipping(area);

        //GL11.glColor4f(1, 1, 1, 0.5F);

        drawForeground(renderer, mouseX, mouseY, partialTick);

        if (area != null)
            renderer.endClipping(area);

        renderer.next();

        for (IControlComponent c : controlComponents)
            c.draw(renderer, mouseX, mouseY, partialTick);

        GL11.glPopAttrib();
    }

    /**
     * Gets the property string.
     *
     * @return the property string
     */
    public String getPropertyString() {
        return "parent=" + (parent != null ? parent.getClass().getSimpleName() : "null") + ", size=" + width + ","
                + height + " | position=" + x + "," + y + " | container=" + parentX() + "," + parentY()
                + " | screen=" + screenX() + "," + screenY();
    }

    @Override
    public String toString() {
        return (this.name == null ? getClass().getSimpleName() : this.name) + " [" + getPropertyString() + "]";
    }

    /**
     * Called first when drawing this {@link UIComponent}.
     *
     * @param renderer the renderer
     * @param mouseX the mouse x
     * @param mouseY the mouse y
     * @param partialTick the partial tick
     */
    public abstract void drawBackground(GuiRenderer renderer, int mouseX, int mouseY, float partialTick);

    /**
     * Called last when drawing this {@link UIComponent}.
     *
     * @param renderer the renderer
     * @param mouseX the mouse x
     * @param mouseY the mouse y
     * @param partialTick the partial tick
     */
    public abstract void drawForeground(GuiRenderer renderer, int mouseX, int mouseY, float partialTick);

}