Package com.threerings.stage.client

Source Code of com.threerings.stage.client.StageScenePanel$PortalObjectTile

//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.stage.client;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import com.samskivert.util.Tuple;

import com.samskivert.swing.Controller;
import com.samskivert.swing.ControllerProvider;
import com.samskivert.swing.util.SwingUtil;

import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;

import com.threerings.media.tile.ObjectTile;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.UniformTileSet;

import com.threerings.miso.client.MisoScenePanel;
import com.threerings.miso.client.SceneObject;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.util.MisoUtil;

import com.threerings.whirled.data.SceneUpdate;
import com.threerings.whirled.spot.data.Cluster;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.Portal;
import com.threerings.whirled.spot.data.SceneLocation;

import com.threerings.stage.data.StageLocation;
import com.threerings.stage.data.StageMisoSceneModel;
import com.threerings.stage.data.StageScene;
import com.threerings.stage.util.StageContext;
import com.threerings.stage.util.StageSceneUtil;

import static com.threerings.stage.Log.log;

/**
* Extends the basic Miso scene panel with Stage fun stuff like portals, clusters and locations.
*/
public class StageScenePanel extends MisoScenePanel
    implements ControllerProvider, KeyListener, PlaceView
{
    /** An action command generated when the user clicks on a location within the scene. */
    public static final String LOCATION_CLICKED = "LocationClicked";

    /** An action command generated when a cluster is clicked. */
    public static final String CLUSTER_CLICKED = "ClusterClicked";

    /** Show flag that indicates we should show all clusters. */
    public static final int SHOW_CLUSTERS = (1 << 1);

    /** Show flag that indicates we should render known land plots
     * (expensive, don't turn this on willy nilly). */
    public static final int SHOW_PLOTS = (1 << 2);

    /**
     * Constructs a stage scene view panel.
     */
    public StageScenePanel (StageContext ctx, Controller ctrl)
    {
        super(ctx, StageSceneUtil.getMetrics());

        // keep these around for later
        _ctx = ctx;
        _ctrl = ctrl;
        _ctrl.setControlledPanel(this);

        // no layout manager
        setLayout(null);
    }

    /**
     * Get the tileset colorizer in use in this scene.
     */
    public SceneColorizer getColorizer ()
    {
        return _rizer;
    }

    @Override
    protected TileSet.Colorizer getColorizer (ObjectInfo oinfo)
    {
        return _rizer.getColorizer(oinfo);
    }

    /**
     * Returns the scene being displayed by this panel. Do not modify it.
     */
    public StageScene getScene ()
    {
        return _scene;
    }

    /**
     * Sets the scene managed by the panel.
     */
    public void setScene (StageScene scene)
    {
        _scene = scene;
        if (_scene != null) {
            _rizer = new SceneColorizer(_ctx.getColorPository(), scene);
            recomputePortals();
            setSceneModel(StageMisoSceneModel.getSceneModel(scene.getSceneModel()));
        } else {
            log.warning("Zoiks! We can't display a null scene!");
            // TODO: display something to the user letting them know that
            // we're so hosed that we don't even know what time it is
        }
    }

    /**
     * Called when we have received a scene update from the server.
     */
    public void sceneUpdated (SceneUpdate update)
    {
        // recompute the portals as those may well have changed
        recomputePortals();

        // we go ahead and completely replace our scene model which will reload the whole good
        // goddamned business; it is a little shocking to the user, but it's guaranteed to work
        refreshScene();
    }

    /**
     * Computes a set of display objects for the portals in this scene.
     */
    protected void recomputePortals ()
    {
        // create scene objects for our portals
        UniformTileSet ots = loadPortalTileSet();

        _portobjs.clear();
        for (Iterator<Portal> iter = _scene.getPortals(); iter.hasNext(); ) {
            Portal portal = iter.next();
            StageLocation loc = (StageLocation) portal.loc;
            Point p = getScreenCoords(loc.x, loc.y);
            int tx = MisoUtil.fullToTile(loc.x);
            int ty = MisoUtil.fullToTile(loc.y);
            Point ts = MisoUtil.tileToScreen(_metrics, tx, ty, new Point());

//             log.info("Added portal", "portal", portal, "screen", StringUtil.toString(p), "tile",
//                StringUtil.coordsToString(tx, ty), "tscreen", StringUtil.toString(ts));

            ObjectInfo info = new ObjectInfo(0, tx, ty);
            info.action = "portal:" + portal.portalId;

            // TODO: cache me
            ObjectTile tile = new PortalObjectTile(
                ts.x + _metrics.tilehwid - p.x + (PORTAL_ICON_WIDTH / 2),
                ts.y + _metrics.tilehei - p.y + (PORTAL_ICON_HEIGHT / 2));
            tile.setImage(ots.getTileMirage(loc.orient));

            _portobjs.add(new SceneObject(this, info, tile) {
                @Override
                public boolean setHovered (boolean hovered) {
                    ((PortalObjectTile)this.tile).hovered = hovered;
                    return isResponsive();
                }
            });
        }
    }

    @Override
    protected void recomputeVisible ()
    {
        super.recomputeVisible();

        // add our visible portal objects to the list of visible objects
        for (int ii = 0, ll = _portobjs.size(); ii < ll; ii++) {
            SceneObject pobj = _portobjs.get(ii);
            if (pobj.bounds != null && _vbounds.intersects(pobj.bounds)) {
                _vizobjs.add(pobj);
            }
        }
    }

    // documentation inherited from interface ControllerProvider
    public Controller getController ()
    {
        return _ctrl;
    }

    // documentation inherited from interface KeyListener
    public void keyPressed (KeyEvent e)
    {
        if (e.getKeyCode() == KeyEvent.VK_ALT) {
            // display all tooltips
            setShowFlags(SHOW_TIPS, true);
        }
    }

    // documentation inherited from interface KeyListener
    public void keyReleased (KeyEvent e)
    {
        if (e.getKeyCode() == KeyEvent.VK_ALT) {
            // stop displaying all tooltips
            setShowFlags(SHOW_TIPS, defaultShowTips());
        }
    }

    // documentation inherited from interface PlaceView
    public void willEnterPlace (PlaceObject plobj)
    {
    }

    // documentation inherited from interface PlaceView
    public void didLeavePlace (PlaceObject plobj)
    {
    }

    /**
     * Returns true if we should always show the object tooltips by
     * default, false if they should only be shown while the 'Alt' key is
     * depressed.
     */
    protected boolean defaultShowTips ()
    {
        return false;
    }

    // documentation inherited
    public void keyTyped (KeyEvent e)
    {
        // nothing
    }

    @Override
    protected boolean handleMousePressed (Object hobject, MouseEvent event)
    {
        // let our parent have a crack at the old mouse press
        if (super.handleMousePressed(hobject, event)) {
            return true;
        }

        // if the hover object is a cluster, we clicked it!
        if (event.getButton() == MouseEvent.BUTTON1) {
            if (hobject instanceof Cluster) {
                Object actarg = new Tuple<Object, Point>(hobject, event.getPoint());
                Controller.postAction(this, CLUSTER_CLICKED, actarg);
            } else {
                // post an action indicating that we've clicked on a location
                Point lc = MisoUtil.screenToFull(_metrics, event.getX(), event.getY(), new Point());
                Controller.postAction(this, LOCATION_CLICKED,
                                      new StageLocation(lc.x, lc.y, (byte)0));
            }
            return true;
        }
        return false;
    }

    /**
     * Called when our show flags have changed.
     */
    @Override
    protected void showFlagsDidChange (int oldflags)
    {
        super.showFlagsDidChange(oldflags);

        if ((oldflags & SHOW_CLUSTERS) != (_showFlags & SHOW_CLUSTERS)) {
            // dirty every cluster rectangle
            for (Shape shape : _clusters.values()) {
                dirtyCluster(shape);
            }
        }
    }

    /**
     * Called when a real cluster is created or updated in the scene.
     */
    protected void clusterUpdated (Cluster cluster)
    {
        // compute a screen rectangle that contains all possible "spots" in this cluster
        List<SceneLocation> spots = StageSceneUtil.getClusterLocs(cluster);
        Rectangle cbounds = null;
        for (int ii = 0, ll = spots.size(); ii < ll; ii++) {
            StageLocation loc = ((StageLocation) spots.get(ii).loc);
            Point sp = getScreenCoords(loc.x, loc.y);
            if (cbounds == null) {
                cbounds = new Rectangle(sp.x, sp.y, 0, 0);
            } else {
                cbounds.add(sp.x, sp.y);
            }
        }

        if (cbounds == null) {
            // if we found no one actually in this cluster, nix it
            removeCluster(cluster.clusterOid);
        } else {
            // otherwise have the view update the cluster
            updateCluster(cluster, cbounds);
        }
    }

    /**
     * Adds or updates the specified cluster in the view. Metrics will be created that allow the
     * cluster to be rendered and hovered over.
     *
     * @param cluster the cluster record to be added.
     * @param bounds the screen coordinates that bound the occupants of the cluster.
     */
    public void updateCluster (Cluster cluster, Rectangle bounds)
    {
        // dirty any old bounds
        dirtyCluster(cluster);

        // compute the screen coordinate bounds of this cluster
        Shape shape = new Ellipse2D.Float(bounds.x, bounds.y, bounds.width, bounds.height);
        _clusters.put(cluster, shape);

        // if the mouse is inside these bounds, we highlight this cluster
        Shape mshape = new Ellipse2D.Float(
            bounds.x-CLUSTER_SLOP, bounds.y-CLUSTER_SLOP,
            bounds.width+2*CLUSTER_SLOP, bounds.height+2*CLUSTER_SLOP);
        _clusterWells.put(cluster, mshape);

        // dirty our new bounds
        dirtyCluster(shape);
    }

    /**
     * Removes the specified cluster from the view.
     *
     * @return true if such a cluster existed and was removed.
     */
    public boolean removeCluster (int clusterOid)
    {
        Cluster key = new Cluster();
        key.clusterOid = clusterOid;
        _clusterWells.remove(key);
        Shape shape = _clusters.remove(key);
        if (shape == null) {
            return false;
        }

        dirtyCluster(shape);
        // clear out the hover object if this cluster was it
        if (_hobject instanceof Cluster && ((Cluster)_hobject).clusterOid == clusterOid) {
            _hobject = null;
        }
        return true;
    }

    /**
     * A place for subclasses to react to the hover object changing.
     * One of the supplied arguments may be null.
     */
    @Override
    protected void hoverObjectChanged (Object oldHover, Object newHover)
    {
        super.hoverObjectChanged(oldHover, newHover);

        if (oldHover instanceof Cluster) {
            dirtyCluster((Cluster)oldHover);
        }
        if (newHover instanceof Cluster) {
            dirtyCluster((Cluster)newHover);
        }
    }

    /**
     * Gives derived classes a chance to compute a hover object that takes
     * precedence over sprites and actionable objects. If this method
     * returns non-null, no sprite or object hover calculations will be
     * performed and the object returned will become the new hover object.
     */
    @Override
    protected Object computeOverHover (int mx, int my)
    {
        return null;
    }

    /**
     * Gives derived classes a chance to compute a hover object that is
     * used if the mouse is not hovering over a sprite or actionable
     * object. If this method is called, it means that there are no
     * sprites or objects under the mouse. Thus if it returns non-null,
     * the object returned will become the new hover object.
     */
    @Override
    protected Object computeUnderHover (int mx, int my)
    {
        if (!isResponsive()) {
            return null;
        }

        // if the current hover object is a cluster, see if we're still in that cluster
        if (_hobject instanceof Cluster) {
            Cluster cluster = (Cluster)_hobject;
            if (containsPoint(cluster, mx, my)) {
                return cluster;
            }
        }

        // otherwise, check to see if the mouse is in some new cluster
        for (Cluster cluster : _clusters.keySet()) {
            if (containsPoint(cluster, mx, my)) {
                return cluster;
            }
        }

        return null;
    }

    /**
     * Returns true if the specified cluster contains the supplied screen coordinate.
     */
    protected boolean containsPoint (Cluster cluster, int mx, int my)
    {
        Shape shape = _clusterWells.get(cluster);
        return (shape == null) ? false : shape.contains(mx, my);
    }

    /**
     * Dirties the supplied cluster.
     */
    protected void dirtyCluster (Cluster cluster)
    {
        if (cluster != null) {
            dirtyCluster(_clusters.get(cluster));
        }
    }

    /**
     * Dirties the supplied cluster rectangle.
     */
    protected void dirtyCluster (Shape shape)
    {
        if (shape != null) {
            Rectangle r = shape.getBounds();
            _remgr.invalidateRegion(
                r.x - (CLUSTER_PAD / 2),
                r.y - (CLUSTER_PAD / 2),
                r.width + (CLUSTER_PAD * 3 / 2),
                r.height + (CLUSTER_PAD * 3 / 2));
        }
    }

    /**
     * Returns the portal at the specified full coordinates or null if no
     * portal exists at said coordinates.
     */
    public Portal getPortal (int fullX, int fullY)
    {
        Iterator<Portal> iter = _scene.getPortals();
        while (iter.hasNext()) {
            Portal portal = iter.next();
            StageLocation loc = (StageLocation) portal.loc;
            if (loc.x == fullX && loc.y == fullY) {
                return portal;
            }
        }
        return null;
    }

    @Override
    protected void paintBaseDecorations (Graphics2D gfx, Rectangle clip)
    {
        super.paintBaseDecorations(gfx, clip);

        paintClusters(gfx, clip);
    }

    /**
     * Paints any visible clusters.
     */
    protected void paintClusters (Graphics2D gfx, Rectangle clip)
    {
        // remember how daddy's things were arranged
        Object oalias = SwingUtil.activateAntiAliasing(gfx);
        Composite ocomp = gfx.getComposite();
        Stroke ostroke = gfx.getStroke();

        // get ready to draw clusters
        gfx.setStroke(CLUSTER_STROKE);
        gfx.setColor(CLUSTER_COLOR);

        if (checkShowFlag(SHOW_CLUSTERS)
            /* || // _alwaysShowClusters.getValue() */) {
            // draw all clusters
            for (Cluster cluster : _clusters.keySet()) {
                drawCluster(gfx, clip, cluster);
            }

        } else if (_hobject instanceof Cluster) {
            // or just draw the active cluster
            drawCluster(gfx, clip, (Cluster)_hobject);
        }

        // put back daddy's things
        gfx.setComposite(ocomp);
        gfx.setStroke(ostroke);
        SwingUtil.restoreAntiAliasing(gfx, oalias);
    }

    /**
     * Draw the cluster specified by the rectangle.
     */
    protected void drawCluster (Graphics2D gfx, Rectangle clip, Cluster cluster)
    {
        Shape shape = _clusters.get(cluster);
        if ((shape != null) && shape.intersects(clip)) {
            if (_hobject == cluster) {
                gfx.setComposite(HIGHLIGHT_ALPHA);
            } else {
                gfx.setComposite(SHOWN_ALPHA);
            }
            gfx.draw(shape);
        }
    }

    /**
     * Returns true if the specified location is associated with a portal.
     */
    protected boolean isPortal (Location loc)
    {
        if (_scene == null) {
            return false;
        }
        Iterator<Portal> iter = _scene.getPortals();
        while (iter.hasNext()) {
            if (loc.equals(iter.next().loc)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Loads up the tileset used to render the portal arrows.
     */
    protected UniformTileSet loadPortalTileSet ()
    {
        return _ctx.getTileManager().loadTileSet(
            "media/stage/portal_arrows.png",
            PORTAL_ICON_WIDTH, PORTAL_ICON_HEIGHT);
    }

    /** Used to render portals as objects in a scene. */
    protected class PortalObjectTile extends ObjectTile
    {
        public boolean hovered;

        public PortalObjectTile (int ox, int oy) {
            setOrigin(ox, oy);
        }

        @Override
        public void paint (Graphics2D gfx, int x, int y) {
            Composite ocomp = gfx.getComposite();
            if (!isResponsive() || !hovered) {
                gfx.setComposite(INACTIVE_PORTAL_ALPHA);
            }
            super.paint(gfx, x, y);
            gfx.setComposite(ocomp);
        }
    }

    /** A reference to our client context. */
    protected StageContext _ctx;

    /** The controller with which we work in tandem. */
    protected Controller _ctrl;

    /** Our currently displayed scene. */
    protected StageScene _scene;

    /** Contains scene objects for our portals. */
    protected List<SceneObject> _portobjs = Lists.newArrayList();

    /** Shapes describing the clusters, indexed by cluster. */
    protected Map<Cluster, Shape> _clusters = Maps.newHashMap();

    /** Shapes describing the clusters, indexed by cluster. */
    protected Map<Cluster, Shape> _clusterWells = Maps.newHashMap();

    /** Handles scene object colorization. */
    protected SceneColorizer _rizer;

//     /** A debug hook that toggles always-on rendering of clusters. */
//     protected static RuntimeAdjust.BooleanAdjust _alwaysShowClusters =
//         new RuntimeAdjust.BooleanAdjust(
//             "Causes all clusters to always be rendered.",
//             "yohoho.miso.always_show_clusters",
//             ClientPrefs.config, false);

    /** The width of the portal icons. */
    protected static final int PORTAL_ICON_WIDTH = 48;

    /** The height of the portal icons. */
    protected static final int PORTAL_ICON_HEIGHT = 48;

    /** The distance within which the mouse must be from a location in order to highlight it. */
    protected static final int MAX_LOCATION_DIST = 25;

    /** The amount the stroke a cluster. */
    protected static final int CLUSTER_PAD = 4;

    /** The width with which to draw the cluster. */
    protected static final Stroke CLUSTER_STROKE = new BasicStroke(CLUSTER_PAD);

    /** The color used to render clusters. */
    protected static final Color CLUSTER_COLOR = Color.ORANGE;

    /** Alpha level used to hightlight locations or clusters. */
    protected static final Composite HIGHLIGHT_ALPHA =
        AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);

    /** Alpha level used to render clusters when they're not selected. */
    protected static final Composite SHOWN_ALPHA =
        AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.15f);

    /** The alpha with which to render inactive portals. */
    protected static final Composite INACTIVE_PORTAL_ALPHA = HIGHLIGHT_ALPHA;

    /** The number of pixels outside a cluster when we assume the mouse is "over" that cluster. */
    protected static final int CLUSTER_SLOP = 25;
}
TOP

Related Classes of com.threerings.stage.client.StageScenePanel$PortalObjectTile

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.