org.ams.testapps.paintandphysics.cardhouse.Tips.java Source code

Java tutorial

Introduction

Here is the source code for org.ams.testapps.paintandphysics.cardhouse.Tips.java

Source

/*
 *
 *  The MIT License (MIT)
 *
 *  Copyright (c) <2015> <Andreas Modahl>
 *
 *  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 org.ams.testapps.paintandphysics.cardhouse;

import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.Array;
import org.ams.core.CoordinateHelper;
import org.ams.core.SceneUtil;
import org.ams.core.Timer;
import org.ams.prettypaint.OutlinePolygon;
import org.ams.prettypaint.PrettyPolygonBatch;

/** Displays tips with an optional arrow that can point at stuff. */
public class Tips {

    private boolean debug = false;

    // for drawing arrows
    private OrthographicCamera arrowCamera;
    private PrettyPolygonBatch polygonBatch;
    private Array<OutlinePolygon> arrows = new Array<OutlinePolygon>();

    private Stage stage;
    private Skin skin;

    // for remembering completed tips
    private Preferences preferences;

    private Array<Runnable> queuedTips = new Array<Runnable>();
    private boolean anyTipVisible = false;

    private long delay = 2000;
    private Timer timer = new Timer();

    private Runnable onResize; // used to lay out actors again

    private Window window;

    /**
     * Displays tips with an optional arrow that can point at stuff.
     *
     * @param key identifier for the preferences class
     *            that is used to remember completed tips.
     */
    public Tips(String key, Stage stage, Skin skin) {
        if (debug)
            Gdx.app.setLogLevel(Application.LOG_DEBUG);
        if (debug)
            debug("Creating a new Tips instance with key" + key + ".");

        this.stage = stage;
        this.skin = skin;

        // for remembering completed tips
        preferences = Gdx.app.getPreferences(key + "Tips");

        // for drawing arrows
        polygonBatch = new PrettyPolygonBatch();

        // camera
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();
        arrowCamera = new OrthographicCamera(10, 10 * (h / w));
        updateArrowCamera();
    }

    private void debug(String text) {
        if (debug)
            Gdx.app.log("Tips", text);
    }

    /** Uncomplete all tips. Removes any visible tip. */
    public void reset() {
        if (debug)
            debug("Resetting.");

        queuedTips.clear();
        timer.clear();
        anyTipVisible = false;
        arrows.clear();
        stage.getActors().removeValue(window, true);

        onResize = null;

        preferences.clear();
        preferences.flush();

    }

    /** Call whenever screen size changes. */
    public void resize(int width, int height) {
        updateArrowCamera();
        if (onResize != null)
            onResize.run();

    }

    /**
     * Dispose all resources and nullify references.
     * Must be called when this object is no longer used.
     */
    public void dispose() {
        if (debug)
            debug("Disposing Tips instance.");

        arrowCamera = null;

        if (polygonBatch != null)
            polygonBatch.dispose();
        polygonBatch = null;

        arrows = null;
        stage = null;
        skin = null;
        queuedTips = null;
        onResize = null;

        preferences = null;

    }

    /** The buttons and text is drawn with the rest of the stage. */
    public void drawArrows() {
        polygonBatch.begin(arrowCamera);
        for (OutlinePolygon outlinePolygon : arrows) {
            outlinePolygon.draw(polygonBatch);
        }
        polygonBatch.end();

        timer.step();
    }

    /** Show the tip first in the queue. */
    private void showNextTip() {
        if (queuedTips.size == 0) {
            anyTipVisible = false;
            onResize = null;
            return;
        }
        anyTipVisible = true;

        queuedTips.first().run();
        queuedTips.removeIndex(0);
    }

    /**
     * When a tip becomes first in the queue
     * it is displayed after {@link #getDelay()} milliseconds.
     */
    public void setDelay(long delay) {
        this.delay = delay;
    }

    /**
     * When a tip becomes first in the queue
     * it is displayed after {@link #getDelay()} milliseconds.
     */
    public long getDelay() {
        return delay;
    }

    /** Whether a tip is currently displayed on the screen. */
    public boolean isAnyTipVisible() {
        return anyTipVisible;
    }

    /** The number of tips waiting to be shown. */
    public int getQueueSize() {
        return queuedTips.size;
    }

    /**
     * If a user clicks "Got it" the tip is forever completed. To uncomplete all tips
     * call {@link #reset()}.
     */
    public boolean isTipCompleted(String key) {
        return preferences.getBoolean(key, false);
    }

    /**
     * If this tip is not completed it is queued for showing. When it becomes first in the queue
     * it is displayed after {@link #getDelay()} milliseconds.
     * <p/>
     * See {@link #setDelay(long)}.
     *
     * @param key     String for remembering whether to display this tips in the feature.
     * @param text    The tips.
     * @param arrowTo If not null an arrow will point to this location.
     */
    public void queueTip(final String key, final String text, final ChangingVector arrowTo) {
        boolean tipComleted = isTipCompleted(key);
        if (debug) {
            if (tipComleted) {
                debug("Not Queueing tooltip with key " + key + ".");
            } else {
                debug("Queueing tooltip with key " + key + ".");
            }
        }
        if (tipComleted)
            return;

        Runnable tip = new Runnable() {
            @Override
            public void run() {

                timer.runOnRender(new Runnable() {
                    long begin = System.currentTimeMillis();

                    @Override
                    public void run() {
                        long now = System.currentTimeMillis();
                        if (now > begin + delay) {
                            timer.remove(this);
                            showTip(key, text, arrowTo);
                        }
                    }
                });

            }
        };

        queuedTips.add(tip);

        if (!anyTipVisible)
            showNextTip();

    }

    /**
     * Instantly show a tip on the screen.
     *
     * @param key     String for remembering whether to display this tips in the feature.
     * @param text    The tips.
     * @param arrowTo If not null an arrow will point to this location.
     */
    private void showTip(final String key, final String text, final ChangingVector arrowTo) {
        if (debug)
            debug("Showing tip with key " + key + ".");

        final Array<OutlinePolygon> arrow = new Array<OutlinePolygon>();

        stage.getActors().removeValue(window, true);

        // update this tip on resize
        Tips.this.onResize = new Runnable() {
            @Override
            public void run() {

                arrows.removeAll(arrow, true);
                stage.getActors().removeValue(window, true);
                showTip(key, text, arrowTo);
            }
        };

        // prepare text table
        Label label = new Label(text, skin);
        label.setColor(Color.BLACK);

        Table textTable = new Table();
        textTable.add(label);

        // prepare button table
        TextButton hide = new TextButton("Hide", skin);
        hide.addListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                stage.getActors().removeValue(window, true);
                arrows.removeAll(arrow, true);
                showNextTip();
            }
        });

        TextButton ok = new TextButton("Got It", skin);
        ok.addListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                stage.getActors().removeValue(window, true);
                arrows.removeAll(arrow, true);
                preferences.putBoolean(key, true);
                preferences.flush();
                showNextTip();
            }
        });

        Table buttonTable = new Table();
        buttonTable.add(hide).pad(SceneUtil.getPreferredPadding(stage));
        buttonTable.add(ok).pad(SceneUtil.getPreferredPadding(stage));

        // put it all in table
        Table mainTable = new Table();

        mainTable.add(textTable).row();
        mainTable.add(buttonTable);

        // prepare arrow
        if (arrowTo != null) {
            float sw = stage.getWidth();
            float sh = stage.getHeight();

            // figure out where to start the arrow
            Vector2 arrowFrom = new Vector2(mainTable.getPrefWidth() * 0.5f, 0);
            Vector2 v = new Vector2(arrowTo.get()).sub(sw * 0.5f, sh * 0.5f);
            arrowFrom.rotateRad(v.angleRad());

            arrowFrom.add(sw * 0.5f, sh * 0.5f);
            arrowFrom.scl(1, -1).add(0, sh);

            // convert to world coordinates
            Vector2 screenCoordinates = stage.stageToScreenCoordinates(arrowFrom);
            Vector2 from = CoordinateHelper.getWorldCoordinates(arrowCamera, screenCoordinates.x,
                    screenCoordinates.y);

            // we know where to end the arrow, just convert to world coordinates
            screenCoordinates = stage.stageToScreenCoordinates(new Vector2(arrowTo.get()).scl(1, -1).add(0, sh));
            Vector2 to = CoordinateHelper.getWorldCoordinates(arrowCamera, screenCoordinates.x,
                    screenCoordinates.y);

            // make and save arrow
            arrow.addAll(makeArrow(from, to));
            arrows.addAll(arrow);
        }

        window = new Window("", skin);
        window.setBackground(new TextureRegionDrawable(skin.getRegion("gray")));
        window.add(mainTable).center();

        window.setSize(mainTable.getPrefWidth(), mainTable.getPrefHeight());

        // center on stage
        float x = stage.getWidth() * 0.5f;
        float y = stage.getHeight() * 0.5f;
        window.setPosition(x, y, Align.center);

        stage.addActor(window);
    }

    /** Update the camera used to draw the arrows. */
    private void updateArrowCamera() {
        float width = Gdx.graphics.getWidth();
        float height = Gdx.graphics.getHeight();

        arrowCamera.setToOrtho(true, 10, 10 * (height / width));
        arrowCamera.zoom = MathUtils.clamp(width / height, 0.25f, 1);
        arrowCamera.update();
    }

    /** Make some OutlinePolygons that form an arrow. */
    private Array<OutlinePolygon> makeArrow(Vector2 from, Vector2 to) {
        if (debug)
            debug("Creating arrow from " + from + " to " + to + ".");

        Array<OutlinePolygon> arrow = new Array<OutlinePolygon>();

        { // make the shaft of the arrow
            Array<Vector2> vertices = new Array<Vector2>();
            vertices.add(from);
            vertices.add(to);
            OutlinePolygon outlinePolygon = new OutlinePolygon();
            outlinePolygon.setVertices(vertices);
            outlinePolygon.setClosedPolygon(false);
            arrow.add(outlinePolygon);
        }
        { // make the tip of the arrow
            Array<Vector2> vertices = new Array<Vector2>();

            Vector2 walk = new Vector2(to).sub(from).nor().scl(0.4f);

            Vector2 one = new Vector2(walk).rotate(150).add(to);
            Vector2 two = new Vector2(walk).scl(0.05f).add(to);
            Vector2 three = new Vector2(walk).rotate(-150).add(to);

            vertices.add(one);
            vertices.add(two);
            vertices.add(three);

            OutlinePolygon outlinePolygon = new OutlinePolygon();
            outlinePolygon.setVertices(vertices);
            outlinePolygon.setClosedPolygon(false);
            arrow.add(outlinePolygon);
        }

        return arrow;
    }

    public interface ChangingVector {
        Vector2 get();
    }
}