Files
narya/src/java/com/threerings/puzzle/client/PuzzleBoardView.java
T
Ray Greenwell f5e5d273e7 Moved ScoreAnimation to parlor/media so that games may make use of it.
Removed "puzzle fonts" from the narya library (lived in PuzzleBoardView).
Left convenience methods in PuzzleBoardView for creation ScoreAnimations,
as we like convenience.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3527 542714f4-19e9-0310-aa3c-eee0fc999fb1
2005-04-28 23:17:29 +00:00

435 lines
14 KiB
Java

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.puzzle.client;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import javax.swing.UIManager;
import com.samskivert.swing.Label;
import com.samskivert.swing.util.SwingUtil;
import com.samskivert.util.StringUtil;
import com.threerings.media.VirtualMediaPanel;
import com.threerings.media.animation.Animation;
import com.threerings.media.animation.AnimationAdapter;
import com.threerings.media.image.Mirage;
import com.threerings.media.sprite.Sprite;
import com.threerings.parlor.media.ScoreAnimation;
import com.threerings.puzzle.Log;
import com.threerings.puzzle.data.Board;
import com.threerings.puzzle.data.PuzzleCodes;
import com.threerings.puzzle.data.PuzzleConfig;
import com.threerings.puzzle.util.PuzzleContext;
/**
* The puzzle board view displays a view of a puzzle game.
*/
public abstract class PuzzleBoardView extends VirtualMediaPanel
{
/**
* Constructs a puzzle board view.
*/
public PuzzleBoardView (PuzzleContext ctx)
{
super(ctx.getFrameManager());
// keep this for later
_ctx = ctx;
}
/**
* Initializes the board with the board dimensions.
*/
public void init (PuzzleConfig config)
{
// save off our bounds
Dimension bounds = getPreferredSize();
_bounds = new Rectangle(0, 0, bounds.width, bounds.height);
}
/**
* Sets the board to be displayed.
*/
public void setBoard (Board board)
{
_board = board;
}
/**
* Provides the board view with a reference to its controller so that
* it may communicate directly rather than by posting actions up the
* interface hierarchy which sometimes fails if the puzzle board view
* is hidden before we get a chance to post our actions.
*/
public void setController (PuzzleController pctrl)
{
_pctrl = pctrl;
}
/**
* Sets the background image displayed by the board view.
*/
public void setBackgroundImage (Mirage image)
{
_background = image;
}
/**
* Set whether this puzzle is paused or not.
* If paused, a label will be displayed with the component's font,
* which may be set with setFont().
*/
public void setPaused (boolean paused)
{
if (paused) {
String pmsg = _pctrl.getPauseString();
pmsg = _ctx.getMessageManager().getBundle(
PuzzleCodes.PUZZLE_MESSAGE_BUNDLE).xlate(pmsg);
// create a label using our component's standard font
_pauseLabel = new Label(pmsg, Label.BOLD | Label.OUTLINE,
Color.WHITE, Color.BLACK,
getFont());
_pauseLabel.setTargetWidth(_bounds.width);
_pauseLabel.layout(this);
} else {
_pauseLabel = null;
}
repaint();
}
/**
* Adds the given animation to the set of animations currently present
* on the board. The animation will be added to a list of action
* animations whose count can be queried with {@link
* #getActionAnimationCount}. The animation will automatically be
* removed from the action list when it completes.
*/
public void addActionAnimation (Animation anim)
{
super.addAnimation(anim);
// remember the animation's existence
_actionAnims.add(anim);
// and listen for it to finish so that we can clear it out
anim.addAnimationObserver(_actionAnimObs);
}
// documentation inherited
public void abortAnimation (Animation anim)
{
super.abortAnimation(anim);
// always check to see if it was action-y
animationFinished(anim);
}
/**
* Called when a potential action animation is finished.
*/
protected void animationFinished (Animation anim)
{
if (DEBUG_ACTION) {
Log.info("Animation cleared " + StringUtil.shortClassName(anim) +
":" + _actionAnims.contains(anim));
}
// if it WAS an action animation, check for a clear
if (_actionAnims.remove(anim)) {
maybeFireCleared();
}
}
/**
* Adds the given sprite to the set of sprites currently present on
* the board. The sprite will be added to a list of action sprites
* whose count can be queried with {@link #getActionSpriteCount}. Callers
* should be sure to remove the sprite when their work with it is done
* via {@link #removeSprite}.
*/
public void addActionSprite (Sprite sprite)
{
// add the piece to the sprite manager
addSprite(sprite);
// note that this piece is interesting
_actionSprites.add(sprite);
}
/**
* Removes the given sprite from the board.
*/
public void removeSprite (Sprite sprite)
{
super.removeSprite(sprite);
if (DEBUG_ACTION) {
Log.info("Sprite cleared " + StringUtil.shortClassName(sprite) +
":" + _actionSprites.contains(sprite));
}
// we just always check to see if it was action-y
if (_actionSprites.remove(sprite)) {
maybeFireCleared();
}
}
// documentation inherited
public void clearSprites ()
{
super.clearSprites();
_actionSprites.clear();
}
// documentation inherited
public void clearAnimations ()
{
super.clearAnimations();
_actionAnims.clear();
}
/**
* Returns the number of action animations on the board.
*/
public int getActionAnimationCount ()
{
return _actionAnims.size();
}
/**
* Returns the number of action sprites on the board.
*/
public int getActionSpriteCount ()
{
return _actionSprites.size();
}
/**
* Returns the count of action sprites and animations on the board.
*/
public int getActionCount ()
{
return _actionSprites.size() + _actionAnims.size();
}
/**
* Dumps to the logs, a list of interesting sprites and animations
* currently active on the puzzle board.
*/
public void dumpActors ()
{
StringUtil.Formatter fmt = new StringUtil.Formatter() {
public String toString (Object obj) {
return StringUtil.shortClassName(obj);
}
};
Log.info("Board contents [board=" + StringUtil.shortClassName(this) +
", sprites=" + StringUtil.listToString(_actionSprites, fmt) +
", anims=" + StringUtil.listToString(_actionAnims, fmt) +
"].");
}
/**
* Creates and returns an animation displaying the given string with
* the specified parameters, floating it a short distance up the view.
*
* @param score the score text to display.
* @param color the color of the text.
* @param font the font whith which to create the score animation.
* @param x the x-position at which the score is to be placed.
* @param y the y-position at which the score is to be placed.
*/
public ScoreAnimation createScoreAnimation (
String score, Color color, Font font, int x, int y)
{
return createScoreAnimation(
ScoreAnimation.createLabel(score, color, font, this), x, y);
}
/**
* Creates a score animation, allowing derived classes to use custom
* animations that are customized following a call to
* {@link #createScoreAnimation}.
*/
protected ScoreAnimation createScoreAnimation (Label label, int x, int y)
{
return new ScoreAnimation(label, x, y);
}
/**
* Positions the supplied animation so as to avoid any active
* animations previously registered with this method, and adds the
* animation to the list of animations to be avoided by any future
* avoid animations.
*/
public void trackAvoidAnimation (Animation anim)
{
// reposition the animation as appropriate
Rectangle abounds = new Rectangle(anim.getBounds());
ArrayList avoidables = (ArrayList)_avoidAnims.clone();
if (SwingUtil.positionRect(abounds, _vbounds, avoidables)) {
anim.setLocation(abounds.x, abounds.y);
}
// add the animation to the list of avoidables
_avoidAnims.add(anim);
// keep an eye on it so that we can remove it when it's finished
anim.addAnimationObserver(_avoidAnimObs);
}
// documentation inherited
public void paintBehind (Graphics2D gfx, Rectangle dirty)
{
super.paintBehind(gfx, dirty);
// render the background
renderBackground(gfx, dirty);
}
/**
* Fills the background of the board with the background color.
*/
protected void renderBackground (Graphics2D gfx, Rectangle dirty)
{
gfx.setColor(getBackground());
gfx.fill(dirty);
}
// documentation inherited
public void paintBetween (Graphics2D gfx, Rectangle dirty)
{
super.paintBetween(gfx, dirty);
// PerformanceMonitor.tick(this, "paint");
renderBoard(gfx, dirty);
}
// documentation inherited
protected void paintInFront (Graphics2D gfx, Rectangle dirty)
{
super.paintInFront(gfx, dirty);
// if the action is paused, indicate as much
if (_pauseLabel != null) {
Dimension d = _pauseLabel.getSize();
_pauseLabel.render(gfx,
_vbounds.x + (_vbounds.width - d.width) / 2,
_vbounds.y + (_vbounds.height - d.height) / 2);
}
}
/**
* Fires a {@link #ACTION_CLEARED} command iff we have no remaining
* interesting sprites or animations.
*/
protected void maybeFireCleared ()
{
if (DEBUG_ACTION) {
Log.info("Maybe firing cleared " +
getActionCount() + ":" + isShowing());
}
if (getActionCount() == 0) {
// we're probably in the middle of a tick() in an
// animationDidFinish() call and we want everyone to finish
// processing their business before we go clearing the action,
// so we queue this up to be run after the tick is complete
_ctx.getClient().getRunQueue().postRunnable(new Runnable() {
public void run () {
_pctrl.boardActionCleared();
}
});
}
}
/**
* Renders the board contents to the given graphics context.
* Sub-classes should implement this method to draw all of their
* game-specific business.
*/
protected abstract void renderBoard (Graphics2D gfx, Rectangle dirty);
/** Our client context. */
protected PuzzleContext _ctx;
/** Our puzzle controller. */
protected PuzzleController _pctrl;
/** The board data to be displayed. */
protected Board _board;
/** The board's bounding rectangle. */
protected Rectangle _bounds;
/** The action animations on the board. */
protected ArrayList _actionAnims = new ArrayList();
/** The action sprites on the board. */
protected ArrayList _actionSprites = new ArrayList();
/** The animations that other animations may wish to avoid. */
protected ArrayList _avoidAnims = new ArrayList();
/** Our background image. */
protected Mirage _background;
/** A label to show when the puzzle is paused. */
protected Label _pauseLabel;
/** The distance in pixels that score animations float. */
protected int _scoreDist = DEFAULT_SCORE_DISTANCE;
/** Listens to our action animations and clears them when they're done. */
protected AnimationAdapter _actionAnimObs = new AnimationAdapter() {
public void animationCompleted (Animation anim, long when) {
animationFinished(anim);
}
};
/** Automatically removes avoid animations when they're done. */
protected AnimationAdapter _avoidAnimObs = new AnimationAdapter() {
public void animationCompleted (Animation anim, long when) {
if (!_avoidAnims.remove(anim)) {
Log.warning("Couldn't remove avoid animation?! " + anim + ".");
}
}
};
/** Temporary action debugging. */
protected static boolean DEBUG_ACTION = false;
// action state constants
protected static final int ACTION_GOING = 0;
protected static final int CLEAR_PENDING = 1;
protected static final int ACTION_CLEARED = 2;
/** The default vertical distance to float score animations. */
protected static final int DEFAULT_SCORE_DISTANCE = 30;
}