Package org.terasology.rendering.nui.internal

Source Code of org.terasology.rendering.nui.internal.CanvasImpl

/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.nui.internal;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.asset.AssetUri;
import org.terasology.asset.Assets;
import org.terasology.engine.Time;
import org.terasology.input.MouseInput;
import org.terasology.math.Border;
import org.terasology.math.Rect2i;
import org.terasology.math.TeraMath;
import org.terasology.math.Vector2i;
import org.terasology.rendering.assets.font.Font;
import org.terasology.rendering.assets.material.Material;
import org.terasology.rendering.assets.mesh.Mesh;
import org.terasology.rendering.assets.texture.Texture;
import org.terasology.rendering.assets.texture.TextureRegion;
import org.terasology.rendering.nui.Color;
import org.terasology.rendering.nui.HorizontalAlign;
import org.terasology.rendering.nui.InteractionListener;
import org.terasology.rendering.nui.NUIManager;
import org.terasology.rendering.nui.ScaleMode;
import org.terasology.rendering.nui.SubRegion;
import org.terasology.rendering.nui.UIWidget;
import org.terasology.rendering.nui.VerticalAlign;
import org.terasology.rendering.nui.skin.UISkin;
import org.terasology.rendering.nui.skin.UIStyle;
import org.terasology.rendering.nui.widgets.UILabel;
import org.terasology.rendering.nui.widgets.UITooltip;
import org.terasology.rendering.opengl.FrameBufferObject;

import javax.vecmath.Quat4f;
import javax.vecmath.Vector3f;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
* @author Immortius
*/
public class CanvasImpl implements CanvasControl {

    private static final Logger logger = LoggerFactory.getLogger(CanvasImpl.class);

    /**
     * The maximum distance the cursor can move between clicks and still be counted as double clicking
     */
    private static final int MAX_DOUBLE_CLICK_DISTANCE = 5;
    /**
     * The maximum time (in milliseconds) between clicks that will still be counted as double clicking
     */
    private static final int DOUBLE_CLICK_TIME = 200;

    /**
     * A sufficiently large value for "unbounded" regions, without risking overflow.
     */
    private static final int LARGE_INT = Integer.MAX_VALUE / 2;

    private final NUIManager nuiManager;
    private final Time time;

    private CanvasState state;

    private Material meshMat = Assets.getMaterial("engine:UILitMesh");
    private Texture whiteTexture = Assets.getTexture("engine:white");

    private List<DrawOperation> drawOnTopOperations = Lists.newArrayList();

    private boolean focusDrawn;

    // Interaction region handling
    private Deque<InteractionRegion> interactionRegions = Queues.newArrayDeque();
    private Set<InteractionRegion> mouseOverRegions = Sets.newLinkedHashSet();
    private InteractionRegion topMouseOverRegion;
    private float tooltipTime;
    private Vector2i lastTooltipPosition = new Vector2i();
    private UITooltip tooltipWidget = new UITooltip();

    private InteractionRegion clickedRegion;

    // Double click handling
    private long lastClickTime;
    private MouseInput lastClickButton;
    private Vector2i lastClickPosition = new Vector2i();

    private CanvasRenderer renderer;

    public CanvasImpl(NUIManager nuiManager, Time time, CanvasRenderer renderer) {
        this.renderer = renderer;
        this.nuiManager = nuiManager;
        this.time = time;
    }

    @Override
    public void preRender() {
        interactionRegions.clear();
        Vector2i size = renderer.getTargetSize();
        state = new CanvasState(null, Rect2i.createFromMinAndSize(0, 0, size.x, size.y));
        renderer.preRender();
        renderer.crop(state.cropRegion);
        focusDrawn = false;
    }

    @Override
    public void postRender() {
        for (DrawOperation operation : drawOnTopOperations) {
            operation.draw();
        }
        drawOnTopOperations.clear();

        if (topMouseOverRegion != null && time.getGameTime() >= tooltipTime && getSkin() != null) {
            tooltipWidget.setAttachment(topMouseOverRegion.getTooltip());
            drawWidget(tooltipWidget);
        } else {
            tooltipWidget.setAttachment(null);
        }

        renderer.postRender();
        if (!focusDrawn) {
            nuiManager.setFocus(null);
        }
    }

    @Override
    public void processMousePosition(Vector2i position) {
        if (clickedRegion != null) {
            Vector2i relPos = new Vector2i(position);
            relPos.sub(clickedRegion.offset);
            clickedRegion.listener.onMouseDrag(relPos);
        }

        Set<InteractionRegion> newMouseOverRegions = Sets.newLinkedHashSet();
        Iterator<InteractionRegion> iter = interactionRegions.descendingIterator();
        while (iter.hasNext()) {
            InteractionRegion next = iter.next();
            if (next.region.contains(position)) {
                Vector2i relPos = new Vector2i(position);
                relPos.sub(next.offset);
                next.listener.onMouseOver(relPos, newMouseOverRegions.isEmpty());
                newMouseOverRegions.add(next);
            }
        }

        for (InteractionRegion region : mouseOverRegions) {
            if (!newMouseOverRegions.contains(region)) {
                region.listener.onMouseLeave();
            }
        }

        if (clickedRegion != null && !interactionRegions.contains(clickedRegion)) {
            clickedRegion = null;
        }

        mouseOverRegions = newMouseOverRegions;

        if (mouseOverRegions.isEmpty()) {
            topMouseOverRegion = null;
        } else {
            InteractionRegion newTopMouseOverRegion = mouseOverRegions.iterator().next();
            if (!newTopMouseOverRegion.equals(topMouseOverRegion)) {
                topMouseOverRegion = newTopMouseOverRegion;
                tooltipTime = time.getGameTime() + newTopMouseOverRegion.element.getTooltipDelay();
                lastTooltipPosition.set(position);
            } else {
                if (lastTooltipPosition.gridDistance(position) > MAX_DOUBLE_CLICK_DISTANCE) {
                    tooltipTime = time.getGameTime() + newTopMouseOverRegion.element.getTooltipDelay();
                    lastTooltipPosition.set(position);
                }
            }

        }
    }

    @Override
    public boolean processMouseClick(MouseInput button, Vector2i pos) {
        boolean possibleDoubleClick = lastClickPosition.gridDistance(pos) < MAX_DOUBLE_CLICK_DISTANCE && lastClickButton == button
                && time.getGameTimeInMs() - lastClickTime < DOUBLE_CLICK_TIME;
        lastClickPosition.set(pos);
        lastClickButton = button;
        lastClickTime = time.getGameTimeInMs();
        for (InteractionRegion next : mouseOverRegions) {
            if (next.region.contains(pos)) {
                Vector2i relPos = new Vector2i(pos);
                relPos.sub(next.offset);
                if (possibleDoubleClick && nuiManager.getFocus() == next.element) {
                    if (next.listener.onMouseDoubleClick(button, relPos)) {
                        clickedRegion = next;
                        return true;
                    }
                } else if (next.listener.onMouseClick(button, relPos)) {
                    clickedRegion = next;
                    nuiManager.setFocus(next.element);
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean processMouseRelease(MouseInput button, Vector2i pos) {
        if (clickedRegion != null) {
            Vector2i relPos = new Vector2i(pos);
            relPos.sub(clickedRegion.region.min());
            clickedRegion.listener.onMouseRelease(button, relPos);
            clickedRegion = null;
            return true;
        }
        return false;
    }

    @Override
    public boolean processMouseWheel(int wheelTurns, Vector2i pos) {
        for (InteractionRegion next : mouseOverRegions) {
            if (next.region.contains(pos)) {
                Vector2i relPos = new Vector2i(pos);
                relPos.sub(next.region.min());
                if (next.listener.onMouseWheel(wheelTurns, relPos)) {
                    clickedRegion = next;
                    nuiManager.setFocus(next.element);
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public SubRegion subRegion(Rect2i region, boolean crop) {
        return new SubRegionImpl(region, crop);
    }

    @Override
    public SubRegion subRegionFBO(AssetUri uri, Vector2i size) {
        return new SubRegionFBOImpl(uri, size);
    }

    @Override
    public void setDrawOnTop(boolean drawOnTop) {
        this.state.drawOnTop = drawOnTop;
    }

    @Override
    public Vector2i size() {
        return new Vector2i(state.drawRegion.width(), state.drawRegion.height());
    }

    @Override
    public Rect2i getRegion() {
        return Rect2i.createFromMinAndSize(0, 0, state.drawRegion.width(), state.drawRegion.height());
    }

    @Override
    public void setAlpha(float value) {
        state.alpha = value;
    }

    @Override
    public void setSkin(UISkin skin) {
        Preconditions.checkNotNull(skin);
        state.skin = skin;
    }

    @Override
    public UISkin getSkin() {
        return state.skin;
    }

    @Override
    public void setFamily(String familyName) {
        state.family = familyName;
    }

    @Override
    public void setMode(String mode) {
        state.mode = mode;
    }

    @Override
    public void setPart(String part) {
        state.part = part;
    }

    @Override
    public UIStyle getCurrentStyle() {
        return state.getCurrentStyle();
    }

    @Override
    public Vector2i calculatePreferredSize(UIWidget widget) {
        return calculateRestrictedSize(widget, new Vector2i(LARGE_INT, LARGE_INT));
    }

    @Override
    public Vector2i calculateRestrictedSize(UIWidget widget, Vector2i sizeRestrictions) {
        if (widget == null) {
            return sizeRestrictions;
        }

        String family = (widget.getFamily() != null) ? widget.getFamily() : state.family;
        UIStyle elementStyle = state.skin.getStyleFor(family, widget.getClass(), UIWidget.BASE_PART, widget.getMode());
        Rect2i region = applyStyleToSize(Rect2i.createFromMinAndSize(Vector2i.zero(), sizeRestrictions), elementStyle);
        try (SubRegion ignored = subRegionForWidget(widget, region, false)) {
            Vector2i preferredSize = widget.getPreferredContentSize(this, elementStyle.getMargin().shrink(sizeRestrictions));
            preferredSize = elementStyle.getMargin().grow(preferredSize);
            return applyStyleToSize(preferredSize, elementStyle);
        }
    }

    @Override
    public Vector2i calculateMaximumSize(UIWidget widget) {
        if (widget == null) {
            return new Vector2i(Integer.MAX_VALUE, Integer.MAX_VALUE);
        }

        String family = (widget.getFamily() != null) ? widget.getFamily() : state.family;
        UIStyle elementStyle = state.skin.getStyleFor(family, widget.getClass(), UIWidget.BASE_PART, widget.getMode());
        try (SubRegion ignored = subRegionForWidget(widget, getRegion(), false)) {
            return applyStyleToSize(elementStyle.getMargin().grow(widget.getMaxContentSize(this)), elementStyle);
        }
    }

    @Override
    public void drawWidget(UIWidget widget) {
        drawWidget(widget, getRegion());
    }

    @Override
    public void drawWidget(UIWidget element, Rect2i region) {
        if (element == null || !element.isVisible()) {
            return;
        }

        if (nuiManager.getFocus() == element) {
            focusDrawn = true;
        }
        String family = (element.getFamily() != null) ? element.getFamily() : state.family;
        UISkin skin = (element.getSkin() != null) ? element.getSkin() : state.skin;
        UIStyle newStyle = skin.getStyleFor(family, element.getClass(), UIWidget.BASE_PART, element.getMode());
        Rect2i regionArea;
        try (SubRegion ignored = subRegionForWidget(element, region, false)) {
            regionArea = applyStyleToSize(region, newStyle, calculateMaximumSize(element));
        }

        try (SubRegion ignored = subRegionForWidget(element, regionArea, false)) {
            if (element.isSkinAppliedByCanvas()) {
                drawBackground();
                try (SubRegion withMargin = subRegionForWidget(element, newStyle.getMargin().shrink(Rect2i.createFromMinAndSize(Vector2i.zero(), regionArea.size())), false)) {
                    element.onDraw(this);
                }
            } else {
                element.onDraw(this);
            }
        }
    }

    private SubRegion subRegionForWidget(UIWidget widget, Rect2i region, boolean crop) {
        SubRegion result = subRegion(region, crop);
        state.element = widget;
        if (widget.getSkin() != null) {
            setSkin(widget.getSkin());
        }
        if (widget.getFamily() != null) {
            setFamily(widget.getFamily());
        }
        setPart(UIWidget.BASE_PART);
        setMode(widget.getMode());
        return result;
    }

    @Override
    public void drawText(String text) {
        drawText(text, state.getRelativeRegion());
    }

    @Override
    public void drawText(String text, Rect2i region) {
        UIStyle style = getCurrentStyle();
        if (style.isTextShadowed()) {
            drawTextRawShadowed(text, style.getFont(), style.getTextColor(), style.getTextShadowColor(), region, style.getHorizontalTextAlignment(),
                    style.getVerticalTextAlignment());
        } else {
            drawTextRaw(text, style.getFont(), style.getTextColor(), region, style.getHorizontalTextAlignment(), style.getVerticalTextAlignment());
        }
    }

    @Override
    public void drawTexture(TextureRegion texture) {
        drawTexture(texture, state.getRelativeRegion());
    }

    @Override
    public void drawTexture(TextureRegion texture, Color color) {
        drawTexture(texture, state.getRelativeRegion(), color);
    }

    @Override
    public void drawTexture(TextureRegion texture, Rect2i region) {
        drawTextureRaw(texture, region, getCurrentStyle().getTextureScaleMode());
    }

    @Override
    public void drawTexture(TextureRegion texture, Rect2i region, Color color) {
        drawTextureRaw(texture, region, color, getCurrentStyle().getTextureScaleMode());
    }

    @Override
    public void drawBackground() {
        Rect2i region = applyStyleToSize(getRegion());
        drawBackground(region);
    }

    private Rect2i applyStyleToSize(Rect2i region) {
        return applyStyleToSize(region, getCurrentStyle());
    }

    private Rect2i applyStyleToSize(Rect2i region, UIStyle style, Vector2i maxSize) {
        if (region.isEmpty()) {
            return region;
        }
        Vector2i size = applyStyleToSize(region.size(), style);
        size.x = Math.min(size.x, maxSize.x);
        size.y = Math.min(size.y, maxSize.y);

        int minX = region.minX() + style.getHorizontalAlignment().getOffset(size.x, region.width());
        int minY = region.minY() + style.getVerticalAlignment().getOffset(size.y, region.height());

        return Rect2i.createFromMinAndSize(minX, minY, size.x, size.y);
    }

    private Vector2i applyStyleToSize(Vector2i size, UIStyle style) {
        Vector2i result = new Vector2i(size);
        if (style.getFixedWidth() != 0) {
            result.x = style.getFixedWidth();
        } else {
            result.x = TeraMath.clamp(result.x, style.getMinWidth(), style.getMaxWidth());
        }

        if (style.getFixedHeight() != 0) {
            result.y = style.getFixedHeight();
        } else {
            result.y = TeraMath.clamp(result.y, style.getMinHeight(), style.getMaxHeight());
        }

        return result;
    }

    private Rect2i applyStyleToSize(Rect2i region, UIStyle style) {
        if (region.isEmpty()) {
            return region;
        }
        Vector2i size = applyStyleToSize(region.size(), style);

        int minX = region.minX() + style.getHorizontalAlignment().getOffset(size.x, region.width());
        int minY = region.minY() + style.getVerticalAlignment().getOffset(size.y, region.height());

        return Rect2i.createFromMinAndSize(minX, minY, size.x, size.y);
    }

    @Override
    public void drawBackground(Rect2i region) {
        if (region.isEmpty()) {
            return;
        }
        UIStyle style = getCurrentStyle();
        if (style.getBackground() != null) {
            if (style.getBackgroundBorder().isEmpty()) {
                drawTextureRaw(style.getBackground(), region, style.getBackgroundScaleMode());
            } else {
                drawTextureRawBordered(style.getBackground(), region, style.getBackgroundBorder(), style.getBackgroundScaleMode() == ScaleMode.TILED);
            }
        }
    }

    @Override
    public void drawTextRaw(String text, Font font, Color color) {
        drawTextRawShadowed(text, font, color, Color.TRANSPARENT);
    }

    @Override
    public void drawTextRaw(String text, Font font, Color color, Rect2i region) {
        drawTextRawShadowed(text, font, color, Color.TRANSPARENT, region);
    }

    @Override
    public void drawTextRaw(String text, Font font, Color color, Rect2i region, HorizontalAlign hAlign, VerticalAlign vAlign) {
        drawTextRawShadowed(text, font, color, Color.TRANSPARENT, region, hAlign, vAlign);
    }

    @Override
    public void drawTextRawShadowed(String text, Font font, Color color, Color shadowColor) {
        drawTextRawShadowed(text, font, color, shadowColor, state.drawRegion);
    }

    @Override
    public void drawTextRawShadowed(String text, Font font, Color color, Color shadowColor, Rect2i region) {
        drawTextRawShadowed(text, font, color, shadowColor, region, HorizontalAlign.LEFT, VerticalAlign.TOP);
    }

    @Override
    public void drawTextRawShadowed(String text, Font font, Color color, Color shadowColor, Rect2i region, HorizontalAlign hAlign, VerticalAlign vAlign) {
        Rect2i absoluteRegion = relativeToAbsolute(region);
        Rect2i cropRegion = absoluteRegion.intersect(state.cropRegion);
        if (!cropRegion.isEmpty()) {
            if (state.drawOnTop) {
                drawOnTopOperations.add(new DrawTextOperation(text, font, hAlign, vAlign, absoluteRegion, cropRegion, color, shadowColor, state.getAlpha()));
            } else {
                renderer.drawText(text, font, hAlign, vAlign, absoluteRegion, color, shadowColor, state.getAlpha());
            }
        }
    }

    @Override
    public void drawTextureRaw(TextureRegion texture, Rect2i region, ScaleMode mode) {
        drawTextureRaw(texture, region, mode, 0f, 0f, 1f, 1f);
    }

    @Override
    public void drawTextureRaw(TextureRegion texture, Rect2i region, Color color, ScaleMode mode) {
        drawTextureRaw(texture, region, color, mode, 0f, 0f, 1f, 1f);
    }

    @Override
    public void drawTextureRaw(TextureRegion texture, Rect2i region, ScaleMode mode, int ux, int uy, int uw, int uh) {
        drawTextureRaw(texture, region, mode,
                (float) ux / texture.getWidth(), (float) uy / texture.getHeight(),
                (float) uw / texture.getWidth(), (float) uh / texture.getHeight());
    }

    @Override
    public void drawTextureRaw(TextureRegion texture, Rect2i region, ScaleMode mode, float ux, float uy, float uw, float uh) {
        drawTextureRaw(texture, region, Color.WHITE, mode, ux, uy, uw, uh);
    }

    @Override
    public void drawTextureRaw(TextureRegion texture, Rect2i region, Color color, ScaleMode mode, float ux, float uy, float uw, float uh) {
        if (!state.cropRegion.overlaps(relativeToAbsolute(region))) {
            return;
        }
        Rect2i absoluteRegion = relativeToAbsolute(region);
        Rect2i cropRegion = absoluteRegion.intersect(state.cropRegion);
        if (!cropRegion.isEmpty()) {
            if (state.drawOnTop) {
                drawOnTopOperations.add(new DrawTextureOperation(texture, color, mode, absoluteRegion, cropRegion, ux, uy, uw, uh, state.getAlpha()));
            } else {
                renderer.drawTexture(texture, color, mode, absoluteRegion, ux, uy, uw, uh, state.getAlpha());
            }
        }
    }

    @Override
    public void drawTextureRawBordered(TextureRegion texture, Rect2i region, Border border, boolean tile) {
        drawTextureRawBordered(texture, region, border, tile, 0f, 0f, 1f, 1f);
    }

    @Override
    public void drawTextureRawBordered(TextureRegion texture, Rect2i region, Border border, boolean tile, int ux, int uy, int uw, int uh) {
        drawTextureRawBordered(texture, region, border, tile,
                (float) ux / texture.getWidth(), (float) uy / texture.getHeight(),
                (float) uw / texture.getWidth(), (float) uh / texture.getHeight());
    }

    @Override
    public void drawTextureRawBordered(TextureRegion texture, Rect2i region, Border border, boolean tile, float ux, float uy, float uw, float uh) {
        if (!state.cropRegion.overlaps(relativeToAbsolute(region))) {
            return;
        }
        Rect2i absoluteRegion = relativeToAbsolute(region);
        Rect2i cropRegion = absoluteRegion.intersect(state.cropRegion);
        if (!cropRegion.isEmpty()) {
            if (state.drawOnTop) {
                drawOnTopOperations.add(new DrawBorderedTextureOperation(texture, absoluteRegion, border, tile, cropRegion, ux, uy, uw, uh, state.getAlpha()));
            } else {
                renderer.drawTextureBordered(texture, absoluteRegion, border, tile, ux, uy, uw, uh, state.getAlpha());
            }
        }
    }

    @Override
    public void drawMaterial(Material material, Rect2i region) {
        Rect2i drawRegion = relativeToAbsolute(region);
        if (!state.cropRegion.overlaps(drawRegion)) {
            return;
        }
        material.setFloat("alpha", state.getAlpha());
        material.bindTextures();
        renderer.drawMaterialAt(material, drawRegion);
    }

    @Override
    public void drawMesh(Mesh mesh, Material material, Rect2i region, Quat4f rotation, Vector3f offset, float scale) {
        if (material == null) {
            logger.warn("Attempted to draw with nonexistent material");
            return;
        }
        if (mesh == null) {
            logger.warn("Attempted to draw nonexistent mesh");
            return;
        }

        Rect2i drawRegion = relativeToAbsolute(region);
        if (!state.cropRegion.overlaps(drawRegion)) {
            return;
        }

        renderer.drawMesh(mesh, material, drawRegion, drawRegion.intersect(state.cropRegion), rotation, offset, scale, state.getAlpha());
    }

    @Override
    public void drawMesh(Mesh mesh, Texture texture, Rect2i region, Quat4f rotation, Vector3f offset, float scale) {
        meshMat.setTexture("texture", texture);
        drawMesh(mesh, meshMat, region, rotation, offset, scale);
    }

    @Override
    public void addInteractionRegion(InteractionListener listener) {
        addInteractionRegion(listener, (UIWidget) null, getCurrentStyle().getMargin().grow(applyStyleToSize(getRegion())));
    }

    @Override
    public void addInteractionRegion(InteractionListener listener, String tooltip) {
        addInteractionRegion(listener, tooltip, getCurrentStyle().getMargin().grow(applyStyleToSize(getRegion())));
    }

    @Override
    public void addInteractionRegion(InteractionListener listener, String tooltip, Rect2i region) {
        UIWidget tooltipLabelWidget = (tooltip == null || tooltip.isEmpty()) ? null : new UILabel(tooltip);
        addInteractionRegion(listener, tooltipLabelWidget, region);
    }

    @Override
    public void addInteractionRegion(InteractionListener listener, Rect2i region) {
        addInteractionRegion(listener, (UIWidget) null, region);
    }

    @Override
    public void addInteractionRegion(InteractionListener listener, UIWidget tooltip) {
        addInteractionRegion(listener, tooltip, getCurrentStyle().getMargin().grow(applyStyleToSize(getRegion())));
    }

    public void addInteractionRegion(InteractionListener listener, UIWidget tooltip, Rect2i region) {
        Vector2i offset = state.drawRegion.min();
        Rect2i finalRegion = state.cropRegion.intersect(relativeToAbsolute(region));
        if (!finalRegion.isEmpty()) {
            listener.setFocusManager(nuiManager);
            if (state.drawOnTop) {
                drawOnTopOperations.add(new DrawInteractionRegionOperation(finalRegion, offset, listener, state.element, tooltip));
            } else {
                interactionRegions.addLast(new InteractionRegion(finalRegion, offset, listener, state.element, tooltip));
            }
        }
    }

    @Override
    public void drawLine(int startX, int startY, int endX, int endY, Color color) {
        int sx = startX + state.drawRegion.minX();
        int sy = startY + state.drawRegion.minY();
        int ex = state.drawRegion.minX() + endX;
        int ey = state.drawRegion.minY() + endY;

        if (state.drawOnTop) {
            drawOnTopOperations.add(new DrawLineOperation(sx, sy, ex, ey, color));
        } else {
            renderer.drawLine(sx, sy, ex, ey, color);
        }
    }

    @Override
    public void drawFilledRectangle(Rect2i region, Color color) {
        drawTexture(whiteTexture, region, color);
    }

    private Rect2i relativeToAbsolute(Rect2i region) {
        return Rect2i.createFromMinAndSize(region.minX() + state.drawRegion.minX(), region.minY() + state.drawRegion.minY(), region.width(), region.height());
    }

    /**
     * The state of the canvas
     */
    private static class CanvasState {
        public UISkin skin = Assets.getSkin("engine:default");
        public String family = "";
        public UIWidget element;
        public String part = "";
        public String mode = "";

        public Rect2i drawRegion;
        public Rect2i cropRegion;

        private float alpha = 1.0f;
        private float baseAlpha = 1.0f;

        private boolean drawOnTop;

        public CanvasState(CanvasState previous, Rect2i drawRegion) {
            this(previous, drawRegion, (previous != null) ? previous.cropRegion : drawRegion);
        }

        public CanvasState(CanvasState previous, Rect2i drawRegion, Rect2i cropRegion) {
            if (previous != null) {
                this.skin = previous.skin;
                this.family = previous.family;
                this.element = previous.element;
                this.part = previous.part;
                this.mode = previous.mode;
                this.drawOnTop = previous.drawOnTop;
                baseAlpha = previous.getAlpha();
            }
            this.drawRegion = drawRegion;
            this.cropRegion = cropRegion;
        }

        public float getAlpha() {
            return alpha * baseAlpha;
        }

        public UIStyle getCurrentStyle() {
            return skin.getStyleFor(family, element.getClass(), part, mode);
        }

        public Rect2i getRelativeRegion() {
            return Rect2i.createFromMinAndSize(0, 0, drawRegion.width(), drawRegion.height());
        }
    }

    /**
     * A SubRegion implementation for this canvas.
     */
    private class SubRegionImpl implements SubRegion {

        public boolean croppingRegion;
        private CanvasState previousState;
        private boolean disposed;

        public SubRegionImpl(Rect2i region, boolean crop) {
            previousState = state;

            int left = TeraMath.addClampAtMax(region.minX(), state.drawRegion.minX());
            int right = TeraMath.addClampAtMax(region.maxX(), state.drawRegion.minX());
            int top = TeraMath.addClampAtMax(region.minY(), state.drawRegion.minY());
            int bottom = TeraMath.addClampAtMax(region.maxY(), state.drawRegion.minY());
            Rect2i subRegion = Rect2i.createFromMinAndMax(left, top, right, bottom);
            if (crop) {
                Rect2i cropRegion = subRegion.intersect(state.cropRegion);
                if (cropRegion.isEmpty()) {
                    state = new CanvasState(state, subRegion, cropRegion);
                } else if (!cropRegion.equals(state.cropRegion)) {
                    state = new CanvasState(state, subRegion, cropRegion);
                    renderer.crop(cropRegion);
                    croppingRegion = true;
                } else {
                    state = new CanvasState(state, subRegion);
                }
            } else {
                state = new CanvasState(state, subRegion);
            }
        }

        @Override
        public void close() {
            if (!disposed) {
                disposed = true;
                if (croppingRegion) {
                    renderer.crop(previousState.cropRegion);
                }
                state = previousState;
            }
        }
    }

    private final class SubRegionFBOImpl implements SubRegion {
        private FrameBufferObject fbo;
        private CanvasState previousState;

        private SubRegionFBOImpl(AssetUri uri, Vector2i size) {
            previousState = state;

            fbo = renderer.getFBO(uri, size);
            state = new CanvasState(state, Rect2i.createFromMinAndSize(new Vector2i(), size));
            fbo.bindFrame();
        }

        @Override
        public void close() {
            fbo.unbindFrame();
            state = previousState;
        }
    }

    private static class InteractionRegion {
        public InteractionListener listener;
        public Rect2i region;
        public Vector2i offset;
        public UIWidget element;
        public UIWidget tooltipOverride;

        public InteractionRegion(Rect2i region, Vector2i offset, InteractionListener listener, UIWidget element, UIWidget tooltipOverride) {
            this.listener = listener;
            this.region = region;
            this.offset = offset;
            this.element = element;
            this.tooltipOverride = tooltipOverride;
        }

        public UIWidget getTooltip() {
            if (tooltipOverride == null) {
                return element.getTooltip();
            }
            return tooltipOverride;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof InteractionRegion) {
                InteractionRegion other = (InteractionRegion) obj;
                return Objects.equals(other.listener, listener);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return listener.hashCode();
        }
    }

    private interface DrawOperation {
        void draw();
    }

    private final class DrawTextureOperation implements DrawOperation {

        private Color color;
        private ScaleMode mode;
        private TextureRegion texture;
        private Rect2i absoluteRegion;
        private Rect2i cropRegion;
        private float ux;
        private float uy;
        private float uw;
        private float uh;
        private float alpha;

        private DrawTextureOperation(TextureRegion texture, Color color, ScaleMode mode, Rect2i absoluteRegion,
                                     Rect2i cropRegion, float ux, float uy, float uw, float uh, float alpha) {
            this.color = color;
            this.mode = mode;
            this.texture = texture;
            this.absoluteRegion = absoluteRegion;
            this.cropRegion = cropRegion;
            this.ux = ux;
            this.uy = uy;
            this.uw = uw;
            this.uh = uh;
            this.alpha = alpha;
        }

        @Override
        public void draw() {
            renderer.crop(cropRegion);
            renderer.drawTexture(texture, color, mode, absoluteRegion, ux, uy, uw, uh, alpha);
            renderer.crop(state.cropRegion);
        }
    }

    private final class DrawBorderedTextureOperation implements DrawOperation {

        private TextureRegion texture;
        private Border border;
        private boolean tile;
        private Rect2i absoluteRegion;
        private Rect2i cropRegion;
        private float ux;
        private float uy;
        private float uw;
        private float uh;
        private float alpha;

        private DrawBorderedTextureOperation(TextureRegion texture, Rect2i absoluteRegion, Border border, boolean tile,
                                             Rect2i cropRegion, float ux, float uy, float uw, float uh, float alpha) {
            this.texture = texture;
            this.tile = tile;
            this.absoluteRegion = absoluteRegion;
            this.border = border;
            this.cropRegion = cropRegion;
            this.ux = ux;
            this.uy = uy;
            this.uw = uw;
            this.uh = uh;
            this.alpha = alpha;
        }

        @Override
        public void draw() {
            renderer.crop(cropRegion);
            renderer.drawTextureBordered(texture, absoluteRegion, border, tile, ux, uy, uw, uh, alpha);
            renderer.crop(state.cropRegion);
        }
    }

    private final class DrawLineOperation implements DrawOperation {

        private int x0;
        private int y0;
        private int x1;
        private int y1;
        private Color color;

        private DrawLineOperation(int x0, int y0, int x1, int y1, Color color) {
            this.x0 = x0;
            this.y0 = y0;
            this.x1 = x1;
            this.y1 = y1;
            this.color = color;
        }

        @Override
        public void draw() {
            renderer.drawLine(x0, y0, x1, y1, color);
        }
    }

    private final class DrawTextOperation implements DrawOperation {
        private String text;
        private Font font;
        private Rect2i absoluteRegion;
        private HorizontalAlign hAlign;
        private VerticalAlign vAlign;
        private Rect2i cropRegion;
        private Color shadowColor;
        private Color color;
        private float alpha;

        private DrawTextOperation(String text, Font font, HorizontalAlign hAlign, VerticalAlign vAlign, Rect2i absoluteRegion, Rect2i cropRegion,
                                  Color color, Color shadowColor, float alpha) {
            this.text = text;
            this.font = font;
            this.absoluteRegion = absoluteRegion;
            this.hAlign = hAlign;
            this.vAlign = vAlign;
            this.cropRegion = cropRegion;
            this.shadowColor = shadowColor;
            this.color = color;
            this.alpha = alpha;
        }

        @Override
        public void draw() {
            renderer.crop(cropRegion);
            renderer.drawText(text, font, hAlign, vAlign, absoluteRegion, color, shadowColor, alpha);
            renderer.crop(state.cropRegion);
        }
    }

    private final class DrawInteractionRegionOperation implements DrawOperation {

        private final Vector2i offset;
        private final Rect2i region;
        private final InteractionListener listener;
        private final UIWidget currentElement;
        private final UIWidget tooltipOverride;

        public DrawInteractionRegionOperation(Rect2i region, Vector2i offset, InteractionListener listener, UIWidget currentElement, UIWidget tooltipOverride) {
            this.region = region;
            this.listener = listener;
            this.offset = offset;
            this.currentElement = currentElement;
            this.tooltipOverride = tooltipOverride;
        }

        @Override
        public void draw() {
            interactionRegions.addLast(new InteractionRegion(region, offset, listener, currentElement, tooltipOverride));
        }
    }

}
TOP

Related Classes of org.terasology.rendering.nui.internal.CanvasImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.