Yet Another jMonkey GUI

This is something I setup for @pspeed.
EDIT: Forgot to mention this is an AppState.

Needs Lemur, slf4j, test-data.

•There is one line in initMiniMap that can be uncommented to show the difference when miniCam is full screen.
• addCollisionRoot is in initMiniMap.
•When miniCam is in the top right corner, click in the top left corner of main view to fire listener.

package mygame;

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.ui.Picture;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.event.CursorButtonEvent;
import com.simsilica.lemur.event.CursorEventControl;
import com.simsilica.lemur.event.DefaultCursorListener;
import com.simsilica.lemur.event.PickState;
import com.simsilica.lemur.style.BaseStyles;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TestGuiNodeState extends BaseAppState implements ActionListener {

    private static final Logger LOG = Logger.getLogger(TestGuiNodeState.class.getName());
    private CollisionResult closest;
    private Geometry mark;
    private Camera miniCam;
    private Node miniMapNode;
    private Node miniMapGuiNode;
    
    @Override
    protected void initialize(Application app) {
                // Create a simple container for our elements
        // Initialize the globals access so that the defualt
        // components can find what they need.
        GuiGlobals.initialize(app);
            
        // Load the 'glass' style
        BaseStyles.loadGlassStyle();
            
        // Set 'glass' as the default style when not specified
        GuiGlobals.getInstance().getStyles().setDefaultStyle("glass");
        
        Box b = new Box(1, 1, 1);
        Geometry geom = new Geometry("Box", b);

        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Green);
        geom.setMaterial(mat);
        
        Node terrainNode = new Node("terrainNode");
        terrainNode.setLocalTranslation(new Vector3f(-3.0f, 0.0f, 0.0f));
        ((SimpleApplication) app).getRootNode().attachChild(terrainNode);
        terrainNode.attachChild(geom);
        
        //setup minimap Nodes
        miniMapNode           = new Node("miniMapNode"); //self managed
        miniMapGuiNode        = new Node("miniMapGuiNode"); //self managed
        miniMapGuiNode.setQueueBucket(RenderQueue.Bucket.Gui);
        miniMapGuiNode.setCullHint(Spatial.CullHint.Never);
                
        intiMiniMap(terrainNode);
        initKeys();
        initMark();
    }
    
    private void intiMiniMap(Node terrainNode) {
        
        miniCam = getApplication().getCamera().clone();
        miniCam.lookAt(terrainNode.getLocalTranslation(), miniCam.getUp().clone());
        
        //Change to make full screen
        miniCam.setViewPort(.5f, 1.0f, 0.5f, 1.0f);
//        miniCam.setViewPort(0.0f, 1.0f, 0.0f, 1.0f);
        
        ViewPort miniMapViewPort = getApplication().getRenderManager().createMainView("MiniMapView", miniCam);
        miniMapViewPort.setClearFlags(true, true, true);
        miniMapViewPort.setBackgroundColor(ColorRGBA.Gray);

        //Attach scenes
        miniMapViewPort.attachScene(miniMapNode);
        miniMapViewPort.attachScene(terrainNode);
        
        // Create a new cam for the guiNode
        Camera miniMapGuiCam = miniCam.clone();

        ViewPort miniMapGuiViewPort = getApplication().getRenderManager().createPostView("MiniMapGuiViewPort", miniMapGuiCam);
        miniMapGuiViewPort.setClearFlags(false, false, false);
        
        //Attach scenes
        miniMapGuiViewPort.attachScene(miniMapGuiNode);  
        
        //Add picking to the guiNode
        getStateManager().getState(PickState.class).addCollisionRoot(miniMapGuiNode, miniMapGuiViewPort, PickState.PICK_LAYER_GUI);

        Node monkeyNode = addButton("Monkey", "Interface/icons/SmartMonkey128.png", 128, 128, 0.15f, 0.85f, true, miniCam);
        miniMapGuiNode.attachChild(monkeyNode);

        CursorEventControl.addListenersToSpatial(monkeyNode, new DefaultCursorListener() {
            @Override
            protected void click( CursorButtonEvent event, Spatial target, Spatial capture ) {
                System.out.println("I've been clicked:" + target + " " + event.getViewPort().getName() + " " + event.getLocation() );
            }
        });
    }
    
    private void initKeys() {
        getApplication().getInputManager().addMapping("Pick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        getApplication().getInputManager().addListener(this, "Pick");
    }
    
    /**
     * A red ball that marks the last spot that was "hit" by the "shot".
     */
    protected void initMark() {
        Sphere sphere = new Sphere(30, 30, 0.1f);
        mark = new Geometry("BOOM!", sphere);
        Material mark_mat = new Material(getApplication().getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        mark_mat.setColor("Color", ColorRGBA.Red);
        mark.setMaterial(mark_mat);
    }
    
        /**
     * Creates and positions a picture to be attached to a gui node.
     * @param name the string name for the picture. Will be appended with the 
     * word "Picture".
     * @param asset the path of the asset to be used.
     * @param butWidth the width of the picture. Scales to this width if not 
     * the actual width of image size.
     * @param butHeight the height of the picture. Scales to this height if not 
     * the actual height of image size.
     * @param screenWidth the position to place the picture in the cameras 
     * view. Must be a float between 0 and 1 where 0 = all the way to the left 
     * side of the view and 1 = all the way to the right side of the view. 
     * @param screenHeight the position to place the picture in the cameras 
     * view. Must be a float between 0 and 1 where 0 = all the way to the bottom 
     * of the view and 1 = all the way to the top of the view.
     * @param alpha set to true if you want to use the supplied images Alpha 
     * channel.
     * @param camera Cam used to calculate node position.
     * @return returns a node that needs to be attached to a gui node.
     */
    public Node addButton(String name, String asset, int butWidth, int butHeight, 
            float screenWidth, float screenHeight, boolean alpha, Camera camera) {
        
        int width = (int) (camera.getWidth() * screenWidth);
        int height = (int) (camera.getHeight()* screenHeight);

        //Image to use for the button.
        Picture button = new Picture(name + "Picture");
        button.setImage(getApplication().getAssetManager(), asset, alpha);
        button.setWidth(butWidth);
        button.setHeight(butHeight);
        
        //Center the image.
        button.center();
        //Add to the GUI bucket.
        button.setQueueBucket(RenderQueue.Bucket.Gui);

        Node node = new Node(name + "Node");
        node.attachChild(button);
        node.setLocalTranslation(width, height, 0);
        
        return node;
    }
    
        /**
     * Checks if the cursor position is within the given ViewPort.
     * @param vp ViewPort to check cursor position against.
     * @return true if the cursor is within the supplied ViewPort.
     */
    private boolean miniViewHasFocus(ViewPort vp) {
        
        //No ViewPort to check.
        if (vp == null) {
            LOG.log(Level.SEVERE, "viewPortHasFocus: Null ViewPort.");
            return false;
        }
        
        float x1   = vp.getCamera().getViewPortLeft();
        float x2   = vp.getCamera().getViewPortRight();
        float y1   = vp.getCamera().getViewPortBottom();
        float y2   = vp.getCamera().getViewPortTop();
        int width  = vp.getCamera().getWidth();
        int height = vp.getCamera().getHeight();
        
        float x = getApplication().getInputManager().getCursorPosition().getX();
        float y = getApplication().getInputManager().getCursorPosition().getY();
        
        return x >= (x1*width) && x <= (x2*width) && y >= (y1*height) && y <= (y2*height);
    }

    @Override
    protected void cleanup(Application app) {
        //TODO: clean up what you initialized in the initialize method,
        //e.g. remove all spatials from rootNode
    }

    //onEnable()/onDisable() can be used for managing things that should 
    //only exist while the state is enabled. Prime examples would be scene 
    //graph attachment or input listener attachment.
    @Override
    protected void onEnable() {
        //Called when the state is fully enabled, ie: is attached and 
        //isEnabled() is true or when the setEnabled() status changes after the 
        //state is attached.
    }

    @Override
    protected void onDisable() {
        //Called when the state was previously enabled but is now disabled 
        //either because setEnabled(false) was called or the state is being 
        //cleaned up.
    }
    
    @Override
    public void update(float tpf) {
        miniMapNode.updateLogicalState(tpf);
        miniMapGuiNode.updateLogicalState(tpf);
    }
    
    @Override
    public void render(RenderManager rm) {
        miniMapNode.updateGeometricState();
        miniMapGuiNode.updateGeometricState();
    }

    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        if (name.equals("Pick") && !isPressed && !miniViewHasFocus(getApplication().getRenderManager().getMainView("MiniMapView"))) {
            CollisionResults results = new CollisionResults();
            Vector2f click2d = getApplication().getInputManager().getCursorPosition().clone();
            Vector3f click3d = getApplication().getCamera().getWorldCoordinates(click2d, 0f).clone();
            Vector3f dir = getApplication().getCamera().getWorldCoordinates(
                    click2d, 1f).subtractLocal(click3d).normalizeLocal();
            Ray ray = new Ray(click3d, dir);
            ((SimpleApplication) getApplication()).getRootNode().collideWith(ray, results);

            for (int i = 0; i < results.size(); i++) {
                // For each hit, we know distance, impact point, name of geometry.
                float dist = results.getCollision(i).getDistance();
                Vector3f pt = results.getCollision(i).getContactPoint();
                String hit = results.getCollision(i).getGeometry().getName();
                System.out.println("* Collision #" + i);
                System.out.println(
                        "  You shot " + hit
                        + " at " + pt
                        + ", " + dist + " wu away.");
            }

            if (results.size() > 0) {
                // The closest collision point is what was truly hit:
                closest = results.getClosestCollision();
                // Let's interact - we mark the hit with a red dot.
                mark.setLocalTranslation(closest.getContactPoint());
                ((SimpleApplication) getApplication()).getRootNode().attachChild(mark);
            } else {
                // No hits? Then remove the red mark.
                ((SimpleApplication) getApplication()).getRootNode().detachChild(mark);
            }
        }
        if (name.equals("Pick") && !isPressed && miniViewHasFocus(getApplication().getRenderManager().getMainView("MiniMapView"))) {
            CollisionResults results = new CollisionResults();
            Vector2f click2d = getApplication().getInputManager().getCursorPosition().clone();
            Vector3f click3d = miniCam.getWorldCoordinates(click2d, 0f).clone();
            Vector3f dir = miniCam.getWorldCoordinates(
                    click2d, 1f).subtractLocal(click3d).normalizeLocal();
            Ray ray = new Ray(click3d, dir);
            ((SimpleApplication) getApplication()).getRootNode().collideWith(ray, results);

            for (int i = 0; i < results.size(); i++) {
                // For each hit, we know distance, impact point, name of geometry.
                float dist = results.getCollision(i).getDistance();
                Vector3f pt = results.getCollision(i).getContactPoint();
                String hit = results.getCollision(i).getGeometry().getName();
                System.out.println("* Collision #" + i);
                System.out.println(
                        "  You shot miniMap " + hit
                        + " at " + pt
                        + ", " + dist + " wu away.");
            }

            if (results.size() > 0) {
                // The closest collision point is what was truly hit:
                closest = results.getClosestCollision();
                // Let's interact - we mark the hit with a red dot.
                mark.setLocalTranslation(closest.getContactPoint());
                miniMapNode.attachChild(mark);
                
            } else {
                // No hits? Then remove the red mark.
                miniMapNode.detachChild(mark);
            }
        }
    }
    
}