Files
narya/src/java/com/threerings/media/animation/AnimatedPanel.java
T
Michael Bayne ad5a5c4e6d Added (and commented out) some handy debug logging.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1099 542714f4-19e9-0310-aa3c-eee0fc999fb1
2002-03-08 01:37:51 +00:00

420 lines
14 KiB
Java

//
// $Id: AnimatedPanel.java,v 1.18 2002/03/08 01:37:51 mdb Exp $
package com.threerings.media.animation;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.image.BufferStrategy;
import java.awt.image.VolatileImage;
import java.util.List;
import com.samskivert.util.Histogram;
import com.samskivert.util.StringUtil;
import com.threerings.media.Log;
import com.threerings.media.sprite.SpriteManager;
/**
* The animated panel provides a useful extensible implementation of a
* {@link Canvas} that implements the {@link AnimatedView} interface. It
* takes care of automatically creating an animation manager and a sprite
* manager to manage the animations that take place within this panel. If
* a sprite manager is not needed, {@link #needsSpriteManager} can be
* overridden to prevent it from being created.
*
* <p> Users of the animated panel must be sure to call {@link #stop} when
* their panel is no longer being displayed and {@link #start} to start it
* back up again when it is once again being displayed. These methods
* deactivate and reactivate the underlying animation manager.
*
* <p> Sub-classes should override {@link #render} to draw their
* panel-specific contents.
*/
public class AnimatedPanel extends Canvas implements AnimatedView
{
/**
* Constructs an animated panel.
*/
public AnimatedPanel ()
{
// create our animation manager
_animmgr = new AnimationManager(this);
// create a sprite manager if we haven't been requested not to
if (needsSpriteManager()) {
_spritemgr = new SpriteManager();
_animmgr.setSpriteManager(_spritemgr);
}
}
/**
* Derived classes can override this method to prevent the automatic
* creation of a sprite manager to work with the animation manager
* that is managing this panel.
*/
protected boolean needsSpriteManager ()
{
return true;
}
/**
* Starts up the animation manager associated with this panel.
*/
public void start ()
{
_animmgr.start();
}
/**
* Shuts down the animation manager associated with this panel.
*/
public void stop ()
{
_animmgr.stop();
}
/**
* Instructs the view to begin scrolling with the specified velocities
* in milliseconds per pixel. A setting of zero indicates that
* scrolling should be deactivated in that direction. Negative values
* mean that the view should be scrolled one pixel in the opposite
* direction in the positive number of milliseconds.
*/
public void setScrolling (int msppx, int msppy)
{
// set our scrolling parameters
_msppx = msppx;
_msppy = msppy;
_lastx = _lasty = 0;
// if the velocities are zero, stop the scrolling, otherwise make
// a note of the time at which scrolling started
if (msppx == 0 && msppy == 0) {
_stime = 0;
_animmgr.setScrolling(0);
} else {
_stime = System.currentTimeMillis();
// set our scrolling speed to the (absolute value of the)
// lower of the two velocities (but not to zero if either one
// is zero)
if (_msppx == 0) {
_animmgr.setScrolling(Math.abs(msppy));
} else if (_msppy == 0) {
_animmgr.setScrolling(Math.abs(msppx));
} else {
_animmgr.setScrolling(
Math.min(Math.abs(msppx), Math.abs(msppy)));
}
}
}
// documentation inherited
public void paint (Graphics g)
{
// we don't paint directly any more, we pass the dirty rect to the
// animation manager to queue up along with our next repaint
Rectangle dirty = g.getClipBounds();
// we need to adjust the dirty rect by our viewport offset before
// passing it to the animation manager
dirty.translate(_tx, _ty);
_animmgr.addDirtyRect(dirty);
}
// documentation inherited
public void update (Graphics g)
{
// the normal Canvas.update() fills itself with its background
// color before calling paint() which we definitely don't want
paint(g);
}
// documentation inherited
public void validate ()
{
super.validate();
// if we change size, clear out our old back buffer
_backimg = null;
// figure out our viewport offsets
Dimension size = getSize(), vsize = getViewSize();
_tx = (vsize.width - size.width)/2;
_ty = (vsize.height - size.height)/2;
// Log.info("Size: " + size + ", vsize: " + vsize +
// ", tx: " + _ty + ", ty: " + _ty + ".");
}
// documentation inherited
public void invalidateRect (Rectangle invalidRect)
{
// pass it on to the animation manager
_animmgr.addDirtyRect(invalidRect);
}
// documentation inherited
public void paintImmediately (List invalidRects)
{
// no use in painting if we're not showing or if we've not yet
// been validated
if (!isValid() || !isShowing()) {
return;
}
// track how long it was since we were last painted
long now = System.currentTimeMillis();
// if (_last != 0) {
// int delta = (int)(now-_last);
// _histo.addValue(delta);
// // dump the histogram every ten seconds
// if (_last % (10*1000) < 50) {
// Log.info("Render histogram.");
// Log.info(StringUtil.toMatrixString(_histo.getBuckets(), 10, 3));
// }
// }
// _last = now;
int width = getWidth(), height = getHeight();
// if scrolling is enabled, determine the scrolling delta to be
// used and do the business
int dx = 0, dy = 0;
if (_stime != 0) {
// compute the total distance scrolled since we started (to
// avoid rounding errors)
// long now = System.currentTimeMillis();
// determine how many pixels further along we've moved and
// make a note of our latest position
if (_msppx != 0) {
int xdist = (int)((now - _stime) / _msppx);
dx = (xdist - _lastx);
_lastx = xdist;
}
if (_msppy != 0) {
int ydist = (int)((now - _stime) / _msppy);
dy = (ydist - _lasty);
_lasty = ydist;
}
// and add invalid rectangles for the exposed areas
if (dx > 0) {
Rectangle dirty = new Rectangle(width - dx, 0, dx, height);
dirty.translate(_tx, _ty);
invalidRects.add(dirty);
} else if (dx < 0) {
Rectangle dirty = new Rectangle(0, 0, -dx, height);
dirty.translate(_tx, _ty);
invalidRects.add(dirty);
}
if (dy > 0) {
Rectangle dirty = new Rectangle(0, height - dy, width, dy);
dirty.translate(_tx, _ty);
invalidRects.add(dirty);
} else if (dy < 0) {
Rectangle dirty = new Rectangle(0, 0, width, -dy);
dirty.translate(_tx, _ty);
invalidRects.add(dirty);
}
// make sure we're actually scrolling before telling people
// about it
if (dx != 0 || dy != 0) {
// if we are working with a sprite manager, let it know
// that we're about to scroll out from under its sprites
// and allow it to provide us with more dirty rects
if (_spritemgr != null) {
_spritemgr.viewWillScroll(dx, dy, invalidRects);
}
// let our derived classes do whatever they need to do to
// prepare to be scrolled
viewWillScroll(dx, dy, now, invalidRects);
}
}
// if we didn't scroll and have no invalid rects, there's no need
// to repaint anything
if (invalidRects.size() == 0 && dx == 0 && dy == 0) {
return;
}
// create our off-screen buffer if necessary
GraphicsConfiguration gc = getGraphicsConfiguration();
if (_backimg == null) {
createBackBuffer(gc);
}
// render into our back buffer
do {
// make sure our back buffer hasn't disappeared
int valres = _backimg.validate(gc);
// if we've changed resolutions, recreate the buffer
if (valres == VolatileImage.IMAGE_INCOMPATIBLE) {
Log.info("Back buffer incompatible, recreating.");
createBackBuffer(gc);
}
Graphics g = null;
try {
g = _backimg.getGraphics();
// if the image wasn't A-OK, we need to rerender the whole
// business rather than just the dirty parts
if (valres != VolatileImage.IMAGE_OK) {
invalidRects.clear();
invalidRects.add(new Rectangle(_tx, _ty, width, height));
Log.info("Lost back buffer, redrawing.");
} else if (dx != 0 || dy != 0) {
// if it was OK, we may need to do some scrolling
g.copyArea(0, 0, width, height, -dx, -dy);
}
// translate into happy space
g.translate(-_tx, -_ty);
// now do our actual rendering
render((Graphics2D)g, invalidRects);
// translate back out of happy space
g.translate(_tx, _ty);
} finally {
g.dispose();
}
// draw the back buffer to the screen
try {
g = getGraphics();
// if we're scrolling, we've got to copy the whole image
// to the screen, otherwise we can just copy the dirty
// regions
if (dx != 0 || dy != 0) {
g.drawImage(_backimg, 0, 0, null);
} else {
// iterate through the invalid rectangles, copying
// those areas from the back buffer to the display
int isize = invalidRects.size();
for (int i = 0; i < isize; i++) {
Rectangle rect = (Rectangle)invalidRects.get(i);
// we have to translate out of view coordinates
// before doing the actual copy
rect.translate(-_tx, -_ty);
g.setClip(rect);
g.drawImage(_backimg, 0, 0, null);
}
}
} finally {
if (g != null) {
g.dispose();
}
}
} while (_backimg.contentsLost());
}
/**
* This is called, when the view is scrolling, just before a call to
* {@link #render}. The animated panel will take care of scrolling the
* contents of the offscreen buffer, but the derived class will need
* to do whatever is necessary to prepare to repaint the exposed
* regions as well as update it's own internal state accordingly.
*
* @param dx the distance (in pixels) that the view will scroll in the
* x direction.
* @param dy the distance (in pixels) that the view will scroll in the
* y direction.
* @param now the current time, provided because we have it and
* scrolling views are likely to want to use it in calculating stuff.
* @param invalidRects the list of invalid rectangles which will be
* redrawn; rectangles can be added to this list if necessary.
*/
protected void viewWillScroll (int dx, int dy, long now, List invalidRects)
{
// nothing to do here
}
/**
* Derived classes that wish to operate in a coordinate system based
* on a view size that is larger or smaller than the viewport size
* (the actual dimensions of the animated panel) can override this
* method and return the desired size of the view. The animated panel
* will take this size into account and translate into the view
* coordinate system before calling {@link #render}.
*/
protected Dimension getViewSize ()
{
return getSize();
}
/**
* Requests that the supplied list of invalid rectangles be redrawn in
* the supplied graphics context. The rectangles will be in the view
* coordinate system (which may differ from screen coordinates, see
* {@link #getViewSize}. Sub-classes should override this method to do
* the actual rendering for their display.
*/
protected void render (Graphics2D gfx, List invalidRects)
{
// nothing for now
}
/**
* Creates the off-screen buffer used to perform double buffered
* rendering of the animated panel.
*/
protected void createBackBuffer (GraphicsConfiguration gc)
{
_backimg = gc.createCompatibleVolatileImage(getWidth(), getHeight());
}
/** The animation manager we use in this panel. */
protected AnimationManager _animmgr;
/** The sprite manager in use by this panel. */
protected SpriteManager _spritemgr;
/** The image used to render off-screen. */
protected VolatileImage _backimg;
/** Our viewport offsets. */
protected int _tx, _ty;
/** The scrolling velocity in milliseconds per pixel. */
protected int _msppx, _msppy;
/** The time at which the scrolling velocity was set. */
protected long _stime;
/** Used to determine how many pixels we've scrolled since the
* scrolling velocity was last set. */
protected int _lastx, _lasty;
/** The last time we were rendered. */
protected long _last;
/** A histogram for tracking how frequently we're rendered. */
protected Histogram _histo = new Histogram(0, 1, 100);
/** The number of buffers to use when rendering. */
protected static final int BUFFER_COUNT = 2;
}