/* @(#)SpinningAnimation.java 1 September 2007 */ import edu.neu.ccs.*; import edu.neu.ccs.gui.*; import edu.neu.ccs.util.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import javax.swing.*; public class SpinningAnimation extends DisplayPanel implements WindowConstants { /** The minimum animation component size: 100. */ public static final int SIZE = 100; /** The common panel gap. */ public static final int GAP = 10; /** The frame for the animation. */ protected JPTFrame frame = null; /** The main panel. */ protected TablePanel mainPanel = null; /** The paintable to spin. */ protected Paintable paintable = null; /** The tile to hold the paintable. */ protected Tile tile = null; /** The tile component width. */ protected int width = 0; /** The tile component height. */ protected int height = 0; /** The paintable component to hold the tile. */ protected PaintableComponent component = null; /** The maximum animation delay in milliseconds. */ protected int maxDelay = 1000; /** The minimum animation delay in milliseconds. */ protected int minDelay = 10; /** Whether or not the animation is running. */ protected volatile boolean running = false; /** *

The object for animation synchronization.

* *

When a thread synchronizes on this object, * the thread can complete its tasks before * another thread can change state.

*/ protected Object animationLock = new Object(); /** The action to run the animation in a separate thread. */ protected SimpleThreadedAction runAction = new SimpleThreadedAction("Run Animation") { public void perform() { runAnimation(); } }; /** The action to stop the animation. */ protected SimpleAction stopAction = new SimpleAction("Stop Animation") { public void perform() { stopAnimation(); } }; /** The content for the animation actions panel. */ protected Object[] actionStuff = { runAction, stopAction }; /** The animation actions panel. */ protected HTable actionPanel = new HTable(actionStuff, GAP, GAP, CENTER); /** The slider for the animation step size. */ protected SliderView stepSlider = new SliderView(HORIZONTAL, 1, 10, 5, 180, 1, 0); /** The slider for the animation speed in steps per second. */ protected SliderView speedSlider = new SliderView(HORIZONTAL, 10, 100, 50, 180, 10, 5); /** The content for the slider panel. */ protected Object[][] sliderStuff = { { "Step Size", stepSlider }, { "Steps Per Second", speedSlider } }; /** The slider panel. */ protected TablePanel sliderPanel = new TablePanel(sliderStuff, GAP, GAP, CENTER); /** The rotation direction labels. */ protected String[] directionLabels = { "Clockwise", "Counterclockwise" }; /** The rotation direction radio button layout. */ protected TableLayout directionLayout = new TableLayout(1, 2, GAP, GAP, CENTER); /** The rotation direction radio button panel. */ protected RadioPanel directionPanel = new RadioPanel(directionLabels, directionLayout); /** *

The window closing action.

* *

This action is required to properly terminate * the animation thread if the window is closed * while the animation is running.

*/ protected SimpleAction closingAction = new SimpleAction("Window Closing Action") { public void perform() { closingAction(); } }; /** The title for the frame. */ protected String frameTitle = "Spinning Animation"; /** *

The constructor that requires an Object * that may be converted to a non-null * Paintable using the appropriate method * in ComponentFactory and which sets the * width and height of the animation tile to SIZE.

* *

The object when converted to a paintable will be * automatically centered in the tile.

*/ public SpinningAnimation(Object paintable) { this(paintable, SIZE, SIZE, null); } /** * The constructor that requires an Object * that may be converted to a non-null * Paintable using the appropriate method * in ComponentFactory and which provides * the width and height of the animation tile. The * width and height will be set to at least SIZE if * the given values are smaller. * *

The object when converted to a paintable will be * automatically centered in the tile.

*/ public SpinningAnimation (Object paintable, int width, int height) { this(paintable, width, height, null); } public SpinningAnimation (Object paintable, int width, int height, String frameTitle) { this.paintable = ComponentFactory.makePaintable(paintable); if (this.paintable == null) { String message = "Null Paintable in SpinningAnimation constructor"; addObject(message); } else { this.width = (width >= SIZE) ? width : SIZE; this.height = (height >= SIZE) ? height : SIZE; createTile(); createMainPanel(); } if (frameTitle != null) { this.frameTitle = frameTitle; } createFrame(); } /** Create the animation tile and its component. */ protected void createTile() { paintable.moveCenterTo(width/2, height/2); tile = new Tile(paintable); Rectangle2D bounds = new XRect(0, 0, width, height); tile.setDefaultBounds2D(bounds); component = new PaintableComponent(tile); component.emptyBorder(4); component.lineBorder(Colors.black, 4); } /** Place the GUI elements into the main panel. */ protected void createMainPanel() { mainPanel = new TablePanel(4, 1, GAP, GAP, CENTER); mainPanel.addObject(component, 0, 0); mainPanel.addObject(sliderPanel, 1, 0); mainPanel.addObject(directionPanel, 2, 0); mainPanel.addObject(actionPanel, 3, 0); mainPanel.emptyBorder(GAP); add(mainPanel); } /** Returns the step size. */ protected int getStep() { return stepSlider.getValue(); } /** Returns the speed. */ protected int getSpeed() { return speedSlider.getValue(); } /** * Returns the thread delay * and guarantees that the thread delay is at least minDelay. */ protected int getDelay() { int delay = maxDelay / getSpeed(); return (delay > minDelay) ? delay : minDelay; } /** Perform one animation step. */ protected void animate() { int step = getStep(); if (directionPanel.getSelectedIndex() == 1) step = -step; tile.rotate(step); } /** * The animation loop method that will * execute in a separate thread. */ protected void runAnimation() { // set state for run setRunState(); // perform animation while(true) { synchronized (animationLock) { if (!running) return; animate(); } JPTUtilities.pauseThread(getDelay()); } } /** * The method to stop the animation loop. */ protected void stopAnimation() { // set state for stop setStopState(); // perform any desired cleanup activities } /** *

Set the enable/disable state of the actions * for the Run state.

* *

Set boolean running to true.

*/ protected void setRunState() { synchronized (animationLock) { runAction. setEnabled(false); stopAction.setEnabled(true); running = true; } } /** *

Set boolean running to false.

* *

Set the enable/disable state of the actions * for the Stop state.

*/ protected void setStopState() { synchronized (animationLock) { running = false; stopAction.setEnabled(false); runAction. setEnabled(true); } } /** *

The window closing method.

* *

This method is required to properly terminate * the animation thread if the window is closed * while the animation is running.

*/ protected void closingAction() { synchronized (animationLock) { running = false; if (frame != null) frame.dispose(); } } /** The method to set the window closing action. */ protected void setWindowClosingAction() { // remove default closing operation frame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); // set special closing operation WindowActionAdapter adapter = new WindowActionAdapter(frame); adapter.addWindowClosingAction(closingAction); } /** * The method to create the application frame and * install the window closing behavior. */ protected void createFrame() { frame = frame(frameTitle); setWindowClosingAction(); } /** * For test purposes, launch a spinning animation using a * star-shaped paintable object. */ public static void main(String[] args) { Shape star = RegularShape.star(0, 0, 100, 7, 3); ShapePaintable paintable = new ShapePaintable(star, PaintMode.FILL_DRAW, Color.red); new SpinningAnimation(paintable, 220, 220, "Star Animation"); } }