Back to Homepage / Tutorial

SwingStates' canvas


SwingStates proposes an empty canvas to draw interactive graphical scenes to define new widgets. A Canvas has a rich graphical model and proposes different relations to link graphical shapes and to define the graphical rendering. It offers facilities to program gesture recognition and animations of graphical shapes. Programming interaction with a Canvas is easily done thanks to state machines and tags. Canvas class inherits from JPanel so it can be used like any other Swing widget


Drawing in a canvas

The content of a SwingStates' Canvas is described by a display list containing graphical objects. Objects are displayed in the order of display list, i.e. if an object o1 is before an object o2 in the display list, o1 is rendered below o2.

Each object belonging to the display list is an instance of a subclass of CShape and has the following properties:

The main functions of a Canvas are:

Display list and rendering order

To create a graphical object and add it to the display list, use methods new* of Canvas class:

1 CRectangle rect = canvas.newRectangle(100, 100, 50, 150);
2 CText text = canvas.newText(250, 250, "Hello world", new Font("verdana", Font.PLAIN, 12));

These methods create a graphical object, insert it to the end of canvas' display list and return the created graphical object. For example, ligne 1 is equivalent to:

CRectangle rect = new CRectangle(100, 100, 50, 150);
canvas.addShape(rect);

Adding an object to a canvas inserts it at the end of the list, i.e. it is displayed above all the other objects of the canvas. Methods above, aboveAll, below, belowAll can be used to modify the order of graphical objects in display list.

To put an object o1 above an object o2:

	o1.above(o2);

To put an object o above all other objects:

	o.aboveAll();

Each graphical object has a boolean, drawable, specifying if the object is displayed or not. This is useful when one wants to hide an object temporarily, for instance a menu displayed only when the user invokes it.

Each graphical object o can be clipped by another object clip so it is visible only through its clip (i.e. only the intersection of o with clip is displayed).

Geometry and hierarchy of an object

The geometry of a graphical object is defined by:

The type of an object is defined by its class:

By default, coordinates of an object are relative to the display surface (the Canvas). The surface's origin is at upper left corner and the surface is pixel-unit. Each graphical object can be transformed. These transformations are defined by composing:

Each transformation is relative to a reference point defined by a ratio r. This ratio, r, is defined relative to the bounding box of the object (before the object being transformed). This reference point is not changed when a transformation is applied to the object. By default, this reference point is set to (1/2, 1/2), i.e. the transformations are relative to the center of the bounding box. The reference point (0, 0) corresponds to the upper left point of the bounding box and th reference point (1, 1) corresponds to the lower right point of the bounding box.

Each transformation have two versions:

Finally, a graphical object can have a parent, which is another graphical object belonging to the display list. When an object has a parent, its transform is interpreted relatively to the transform of the parent, i.e. affine transform of an object is cumulated with its parent's transform. For example, it is useful to attach all items of a contextual menu to a common parent so all items can be moved just by moving the parent.

A program to test transformations

The following program illustrates available transformations on graphical objects of the canvas. A control panel allows to modify the current transformations and the reference point for each object. Each object has a ghost on which the transformation is not applied: the effect of each parameter of the transformations is easily visible.

Furthermore, all the objects have a common parent, the red square in the top left corner of the canvas. Drag it with the left mouse button to translate it and drag it with the right button to rotate it. As we explained above, the objects are modified when their parent is modified.

Failed to load applet. No Java 2 SDK, Standard Edition v 1.5.0 support for applet.

smClasses.jpg

See applet sources.

Graphical attributes of an object

The graphical attributes of an object define its rendering. they depend on the type of the object:

Colors are specified by objects that implement java.awt.Paint Java interface. It includes simple colors (class java.awt.Color) but also gradients (class java.awt.GradientPaint) and textures (class java.awt.TexturePaint).

The outline style is specified by an object that implements java.awt.Stroke Java interface. It includes the width of the pen, the shape of the pen and the dotted style (e.g., class java.awt.BasicStroke).

Fonts for CText are specified by java.awt.Font Java class.

Chained syntax

Most of SwingStates methods return the object on which they are called so methods calls can be chained through a single instruction. For example:

	rect.setFillPaint(Color.green).translateTo(50, 50);

replaces the two following instructions:

	rect.setFillPaint(Color.green);
	rect.translateTo(50, 50);

Tags for graphical shapes: grouping and interaction

Tags are a feature to easily manipulate and program interaction with groups of objects (e.g. some shapes can be selected while other not). A graphical object can have several tags and a tag can be added to several graphical objects.

WARNING: Adding a tag is effective only once a graphical object is added to canvas. Calls to method addTag on an object before adding it to a canvas are ignored.

SwingStates proposes two kinds of tag:

Most of the methods available on a CShape are also available on a CTag so all shapes that share a tag t can be modified by modifying t. For example, the following instruction colors and translates all shapes tagged by outlinedShapes:

outlinedShapes.setFillPaint(Color.LIGHT_GREEN).translateBy(50, 50);

Tags are java.util.Iterator so they can also be browsed:

tag.reset();
while(tag.hasNext())
    tag.nextShape().setTransparencyFill((float)Math.random());

This is convenient for enumerating the shapes that share a tag but SwingStates' programmers must take care of not modifying the collection of shapes tagged by t while iterating on t. Note that methods t.addTo(s), t.removeFrom(s) in class CTag modify the collection of shapes tagged by t and that method removeShape(s) in class Canvas modify the collection of shapes of all tags that label s.

Extensional tags can be active: each time an extensional tag is added to (or removed from) a shape, method added (or removed) of tag is called so one can override these methods to specify a given behavior. For example, selected graphical objects can be automatically outlined by a stroke of width 2 using this tag:

CExtensionalTag selected = new CExtensionalTag(canvas) {
    public void added(CShape s) { 
        s.setOutlined(true).setStroke(new BasicStroke(2));
    }
    public void removed(CShape s) {
        s.setStroke(new BasicStroke(1));
    }
};
...
CRectangle rect = canvas.newRectangle(100, 100, 50, 150);
CText text = canvas.newText(250, 250, "Hello world", new Font("verdana", Font.PLAIN, 12));
rect.addTag(selected);
text.addTag(selected);

SwingStates contains a special class of extensional tags, CNamedTag, that can be referenced using a string. For example, one can directly add a tag using a string and get this tag by querying canvas for tag named by this string:

CRectangle rect = canvas.newRectangle(100, 100, 50, 150);
CText text = canvas.newText(250, 250, "Hello world", new Font("verdana", Font.PLAIN, 12));
rect.addTag("selected");
text.addTag("selected");
...
canvas.getTag("selected").setFillPaint(Color.GREEN);

Handling events occurring on a canvas using a CStateMachine

A CStateMachine allows to handle all mouse and keyboard Java events occuring on a SwingStates' canvas. It inherits from BasicInputStateMachine and proposes two other versions for each transition (*OnShape and *OnTag). It allows to easily identify three different contexts for input events: (i) on a shape having a given tag, (ii) on a shape or (iii) anywhere.

*OnShape transitions

Picking is automatically done on a SwingStates' canvas so when an event occurs on a graphical object, e.g. a mouse press, *OnShape matching transition can be fired, e.g. PressOnShape(BUTTON1). The following machine differentiates mouse presses on a graphical object (selecting the object) and mouse presses on the background (creating a new object):

CStateMachine sm = new CStateMachine() {
    public State start = new State() {
        Transition pressOnShape = new PressOnShape(BUTTON1) {
            public void action() {
                getShape().addTag(selected);
            }
        };
        Transition pressOnBackground = new Press(BUTTON1) {
            public void action() {
                canvas.newRectangle(getPoint().getX(), getPoint().getY(), 30, 20);
            }
        };
    };
};
sm.attachTo(canvas);

Note that a mouse press on a graphical object can trigger a PressOnShape transition but also a Press transition. Using these transitions in the order PressOnShape --> Press ensures that any mouse press that triggers Press has not occurred on a shape but using the order Press --> PressOnShape makes transition PressOnShape never triggered since any mouse press always matches the Press transition.

Each shape can be pickable or not (by using method setPickable). By default, every shape is pickable so it is taken into account while performing picking and can thus trigger *OnShape transitions. Restricting the set of pickable shapes to only the shapes that are involved in interaction can really improve performances. For example, shapes used as decorations in the background must not be pickable. Consider we want to highlight a labeled box when the cursor is over it, we write the following code:

// Drawing a CText over a CRectangle to build a labeled box
CText label = canvas.newText(50, 50, "Label");
CRectangle box = canvas.newRectangle(label.getMinX() - 10, label.getMinY() - 10, 20, 20);
label.above(box);

CStateMachine sm = new CStateMachine() {
    Paint initColor;
    public State out = new State() {
        Transition enterBox = new EnterOnShape(">> in") {
            public void action() {
                initColor = getShape().getFillPaint();
                getShape().setFillPaint(Color.YELLOW);
            }
        };
    };
    public State in = new State() {
        Transition leaveBox = new LeaveOnShape(">> out") {
            public void action() {
                getShape().setFillPaint(initColor);
            }
        };
    };
};
sm.attachTo(canvas);

The result is not the one expected: when the mouse enters the label, the label is highlighted and the background unhighlighted. To solve this problem, the label can be set as non pickable so it will be ignored during the picking process and won't trigger EnterOnShape transition.

// Drawing a CText over a CRectangle to build a labeled box
...
label.above(box).setPickable(false);

*OnTag transitions

*OnTag transitions are triggered by events occuring on shapes having a given tag. The tag is specified as an argument of the *OnTag transition. This argument can be the tag itself, the name of the tag (only for CNamedTag tags) or the class of the tag.

For example, the machine in the following code implements a selection mechanism by pressing on graphical shapes (pressing on a shape selects it and pressing on a selected shape deselects it):

CExtensionalTag selected = new CExtensionalTag(canvas) {
    public void added(CShape s) { 
        s.setOutlined(true).setStroke(new BasicStroke(2));
    }
    public void removed(CShape s) {
        s.setStroke(new BasicStroke(1));
    }
};
...
CRectangle rect = canvas.newRectangle(100, 100, 50, 150);
CText text = canvas.newText(250, 250, "Hello world", new Font("verdana", Font.PLAIN, 12));
rect.setOutlined(true);
text.setOutlined(false);
...
CStateMachine sm = new CStateMachine() {
    public State start = new State() {
        Transition pressOnShape = new PressOnTag(selected, BUTTON1) {
            public void action() {
                getShape().removeTag(selected);
            }
        };
        Transition pressOnBackground = new PressOnShape(BUTTON1) {
            public void action() {
                getShape().addTag(selected);
            }
        };
        Transition pressOnBackground = new Press(BUTTON1) {
            public void action() {
                canvas.newRectangle(getPoint().getX(), getPoint().getY(), 30, 20);
            }
        };
    };
};
sm.attachTo(canvas);

The selected tag makes a selected shape be outlined with a stroke of width 2. However, in this version, a shape which was originally not outlined will stay outlined while it has been deselected. Another solution consists in using one tag per shape and reference the set of tags by the class of these tags in the state machine. Each tag stores the original outline state of its single shape and restores it when it is removed from the shape:

class Selected extends CExtensionalTag {
    boolean outlined;
    
    public Selected(Canvas canvas) {
        super(canvas);
    }
    public void added(CShape s) { 
        // The initial visibility of the shape's outline is stored when the tag is added...
        outlined = s.getOutlined();
        s.setOutlined(true).setStroke(new BasicStroke(2));
    }
    public void removed(CShape s) {
        // ...so it can be restored when the tag is removed.
        s.setOutlined(outlined).setStroke(new BasicStroke(1));
    }
};
...
CRectangle rect = canvas.newRectangle(100, 100, 50, 150);
CText text = canvas.newText(250, 250, "Hello world", new Font("verdana", Font.PLAIN, 12));
rect.setOutlined(true);
text.setOutlined(false);
...
CStateMachine sm = new CStateMachine() {
    public State start = new State() {
        // This transition is triggered by any mouse press on a shape having a tag of class Selected
        Transition pressOnShape = new PressOnTag(Selected.class, BUTTON1) {
            public void action() {
                getShape().removeTag(getTag());
            }
        };
        Transition pressOnBackground = new PressOnShape(BUTTON1) {
            public void action() {
                // Attaching a different instance of Selected tag to each shape
                getShape().addTag(new Selected());
            }
        };
        Transition pressOnBackground = new Press(BUTTON1) {
            public void action() {
                canvas.newRectangle(getPoint().getX(), getPoint().getY(), 30, 20);
            }
        };
    };
};
sm.attachTo(canvas);

Attaching a CStateMachine to a shape or a tag

CStateMachines can be attached to only given CElements (method attachTo(CElement element)). A CElement is a shape, a tag or a canvas. Transitions *OnShape and *OnTag will be fired only for pickable shapes which are also parts of the attached CElements.

For example, the transition t1 will be fired only if a mouse press occurs on a shape that holds the tags movable and highlitable while the transition t2 will still be fired by any mouse press event on the canvas:

CStateMachine machine = new CStateMachine() {
   State start = new State() {
      Transition t1 = new PressOnShape(BUTTON1, ">> moveShape") {
      	  public void action() {
      	     highlight(getShape());
      	     ...
      	  }
      };
      Transition t2 = new Press(BUTTON1, ">> panView") {
      	...
      };
   };
   State moveShape = new State() {
      ...
   };
   State panView = new State() {
      ...
   };
   ...
};
...
CTag movable = ...;
CTag highlitable = ...;
...
machine.attachTo(movable);
machine.attachTo(highlitable);

Summary through an example: linear and circular menus

In this section, we build a simple widget in which we can create new shapes, copy, cut and paste shapes using two types of menu: a linear contextual menu invoked by a right click and a marking menu invoked by a left click as illustrated by the applet below. Each menu contains four commands: "new", "cut", "copy" and "paste".

Failed to load applet. No Java 2 SDK, Standard Edition v 1.5.0 support for applet.

smClasses.jpg

See applet sources: LinearMenu, Menu, MarkingMenu, GraphicalEditor and applet.

Linear menu

Let's start by defining the rendering of a linear menu. A menu item is a rectangle and a text inside of it:

CRectangle bgItem = canvas.newRectangle(0, i*20, 50, 20);
bgItem.setFillPaint(Menu.BG_COLOR).setOutlinePaint(Menu.BORDER_COLOR);
CText labelItem = (CText) canvas.newText(0, 0, items[i], Menu.FONT);
// The text is centered vertically on its background rectangle with a pad x of 3 pixels
labelItem.setReferencePoint(0, 0.5).translateTo(3, bgItem.getCenterY());

Because the menu location will vary according to user's mouse presses, we use a parent shape for all graphical objects of a menu that we will translate to move the whole menu.

parent = canvas.newRectangle(-1, -1, 2, 2);
// the parent is not displayed, it is simply use to apply a transform to all menu shapes
parent.setDrawable(false);
parent.addChild(labelItem).addChild(bgItem);

Also, the menu will not always be visible so we attach a tag to each of its graphical elements that we will use to make the menu visible or not.

tagWhole = new CExtensionalTag(canvas) { };
bgItem.addTag(tagWhole);
labelItem.addTag(tagWhole);
...
// hide the whole menu
tagWhole.setDrawable(false);

***

Let's now define interaction with a linear menu. First, we want to define a machine to highlight an item when the mouse cursor passes over it. Highlighting an item means changing the color of its rectangle background so we can add a tag to each background shape that we can use in the state machine for highlighting. To avoid a flickering effect when passes over the text of an item, we set the pickable attribute of text items to false.

bgItem.addTag("menuItem");
labelItem.setPickable(false);
...
CStateMachine hilite = new CStateMachine() {
    public State out = new State() {
        Transition hiliteItem = new EnterOnTag("menuItem", ">> in") {
            public void action() {
                getShape().setFillPaint(Menu.HILITE_COLOR);
            }
        };
    };
    public State in = new State() {
        Transition unhiliteItem = new LeaveOnTag("menuItem", ">> out") {
            public void action() {
                getShape().setFillPaint(Menu.BG_COLOR);
            }
        };
    };
};

Now, we define interaction for selecting an item in the menu. Because we want to be able to retrieve the text of an item, we use one tag by item that contains the text of this item rather than a simple string tag "menuItem".

public class MenuItem extends CNamedTag {
	public MenuItem(String nameItem) {
		super(nameItem);
	} 
}
bgItem.addTag(tagWhole).addTag(new MenuItem(labelItem.getText()));

tagLabels = new CExtensionalTag(canvas) { };
labelItem.addTag(tagLabels);
...
interaction = new CStateMachine() {
    public State menuOff = new State() {
        Transition invoke = new Press(BUTTON3, ">> menuOn") {
            public void action() {
                pInit = getPoint();
                showMenu(pInit);
            }
        };
    };
    
    public State menuOn = new State() {
        Transition select = new ReleaseOnTag(MenuItem.class, BUTTON3, ">> menuOff") {
            public void action() {
                String item = ((MenuItem)getTag()).getName();
                System.out.println(item+" selected");
            }
        };
        Transition out = new Release(">> menuOff") { };
        public void leave() {
            hideMenu();
        }
    };
};

void showMenu(Point2D pt) {	
    // translate the whole menu to pt
    parent.translateTo(pt.getX(), pt.getY());
    // make the whole menu visible and make it interactive (but not item labels)
    tagWhole.setDrawable(true).setPickable(true);
    tagLabels.setPickable(false);
    // put the menu on top of other shapes, make sure item labels above item backgrounds. 
    tagWhole.aboveAll();
    tagLabels.aboveAll();
}

void hideMenu() {
    // make the whole menu visible and make it non interactive 
    // (otherwise its shapes could interfere with other interaction techniques)
    tagWhole.setDrawable(false).setPickable(false);
}

Circular menu

The main difference between the rendering of circular menu and a linear menu is the use of a CPolyLine to have a background as a pie slice instead of a rectangle and use of a clipping disc shape to obtain an empty zone in the center of the menu.

CPolyLine bgItem = canvas.newPolyLine(0, 0).lineTo(50,0).arcTo(0, -angleStep, 50, 50).close(); (*)
CShape clipCircle = (new CEllipse(-50, -50, 100, 100)).getSubtraction(new CEllipse(-30, -30, 60, 60));
bgItem.setClip(clipCircle);

(*) see this page to get further explanations about arcTo method.


Gesture recognition

SwingStates implements a mechanism of gesture recognition. Package fr.lri.swingstates.gestures.rubine implements Rubine's algorithm for gesture recognition [Rubine91] while fr.lri.swingstates.gestures.dollar1 implements algorithm of the $1 recognizer [Wobbrock07]. Both algorithms allow to train a recognizer by drawing series of examples for each class of gestures. This page presents the Training application to create vocabulary of gestures by drawing examples.

Once a classifier is built and saved in a file, e.g. 'vocabulary.cl', it can be loaded in a program using the following instruction:

Classifier classifier = RubineClassifier.newClassifier("vocabulary.cl");
to use a classifier implemented with Rubine's algorithm or the following one:
Classifier classifier = Dollar1Classifier.newClassifier("vocabulary.cl");
to use a $1 classifier.

The method classify of a classifier takes a Gesture as input to output the name of the recognized class. A Gesture is a simple series of timed points that is built using the method addPoint. The following applet recognizes four classes of gestures: "new" (stroke a 'N'), a 'V' for a "cut" gesture, a '/\' for a "copy" and a '/' for a "paste" gesture. inputs gesture using a drag interaction. When the user presses the mouse button, he starts to draw a new gesture. Then each mouse drag defines a new point for the current gesture until mouse button stays pressed. Releasing the mouse button ends the current gesture which is sent to the classifier to get the name of the recognized class. To provide feedback to the user, this applet draws the ink trail of the current gesture by using a CPolyLine on a canvas. The name of the recognized class is printed in the upper left corner. to draw the current gesture to provide feedback. It displays the name of recognized gesture each time the user releases the mouse button.

Failed to load applet. No Java 2 SDK, Standard Edition v 1.5.0 support for applet.

smClasses.jpg

See applet sources.


Animations

SwingStates contains a simple motor of animations for implementing continuous changes for shapes, tags or the whole canvas. Any animation is an Animation object that has the following attributes:

An animation lap lasts duration milliseconds. During an odd lap, the parameter t is smoothly changed from 0 to 1 while during an even lap, t is smoothly changed from 1 to 0 while during an even lap in a lap of duration. The value of t is updated every delay milliseconds and is set according to the pacing function function (linear function of time or sigmoid function of time). By default, an animation is composed of one lap that lasts 1000 milliseconds and t is updated every 40 milliseconds according to a linear pacing function.

SwingStates contains some predefined classes for animating style attributes (AnimationFillPaint, AnimationOutlinePaint...) and geometrical attributes (AnimationScale, AnimationTranslate...). There are two versions for each of the geometrical animations: absolute (e.g. AnimationTranslateTo) and relative (e.g. AnimationTranslateBy). Absolute animations are one-lap animations, e.g. AnimationTranslateTo(x,y) smoothly translates a shape from its current location to (x,y), while relative animations has an infinite number of laps, ignores the value of t and continuously changes a geometrical feature by a delta, e.g. AnimationTranslateTo(dx,dy) continuously translates a shape by vector (dx,dy). Animations for style only exist in absolute version.

For example, the following code progressively fills the background in gray. We first create a pixel unit square and then animate it with an AnimationScaleTo so it fills the whole canvas. Use sliders to test different values for animation parameters and press left mouse button anywhere in the canvas to start the animation.

// a rectangle in the center of view
animatedShape = canvas.newRectangle(100, 150, 1, 1).setOutlined(false);
// an animation to magnify the rectangle so it fills the canvas
animationDemo = new AnimationScaleTo(canvas.getWidth(), canvas.getHeight());
// associate animation to a shape and start animation
animatedShape.animate(animationDemo);

Failed to load applet. No Java 2 SDK, Standard Edition v 1.5.0 support for applet.

smClasses.jpg

See applet sources.

To create your own animations, you can create a class that inherits from Animation and override method step(double t) to implement animation behavior according to t value. This method is called each time the animation is updated. For example, we want to change the color from gray to yellow when the shape fills the view. We extend AnimationTranslateTo class and implement step method to change the filling color:

class ScaleAndColor extends AnimationScaleTo {

	public ReboundingBall(double sx, double sy) {
		super(sx, sy);
	}
	
	public void step(double t) {
	   super.step(t);
	   getAnimated().setFillPaint(new Color(
	           (int)(Color.YELLOW.getRed()*t + Color.LIGHT_GRAY.getRed()*(1-t)),
	           (int)(Color.YELLOW.getGreen()*t + Color.LIGHT_GRAY.getGreen()*(1-t)),
	           (int)(Color.YELLOW.getBlue()*t + Color.LIGHT_GRAY.getBlue()*(1-t))));
    }
}

In the following example, our canvas contains a rebounding ball and we suppose having a ReboundingBall animation that handles collisions on canvas edges:

// ReboundingBall is a subclass of AnimationTranslateBy that handles collisions on canvas edges
AnimationTranslateBy animBall = new ReboundingBall(-10, 10, 400, 300);
CEllipse grayBall = canvas.newEllipse(50, 50, 20, 20);
grayBall.animate(animBall);

Now, we want the user being able to create balls that vertically rebound. He can click on the canvas to create a new ball. We also want that the new created ball first fills in red and then starts to rebound. So we want rebounding animation starts just after filling animation finishes. One way of doing it is by overriding doStop method of our filling animation to make the rebounding animation start. Another solution consists in using Animation* transitions in a state machine. Each time an animation starts, stops, is suspended or is resumed, the respective AnimationStarted, AnimationStopped, AnimationSuspended or AnimationResumed are triggered.

Animation fillRed = new AnimationFillPaint(Color.RED);
fillRed.setLapDuration(2000).setNbLaps(1);

CStateMachine smBall = new CStateMachine() {
			
    ReboundingBall animRebound;
        
    public State idle = new State() {
        Transition newBall = new Press(BUTTON1, ">> prepareBall") {
            public void action() {
                CEllipse newBall = newEllipse(getPoint().getX(), getPoint().getY(), 20, 20);
                fillRed.setAnimatedElement(newBall).start();
            }
        };
    
        Transition endCollide = new AnimationStopped(fillRed, ">> idle") {
            public void action() {
                animRebound = new ReboundingBall(0, 10, 400, 300);
                animRebound.setAnimatedElement(getAnimation().getAnimated()).start();
            }
        };
    };
};

smBall.attachTo(canvas);	

As graphical objects, animations can be tagged. It is especially useful to manage several animations in parallel. On the previous example, we have used only one animation for filling each new created shape. If we create two shapes in a row, the second created shape will make the animation for the first shape stops: animation will restart on second shape and won't finish its job for the first shape. To solve this problem, we create one animation per shape and tag all these animation using the tag animationsFillRed. We use this tag in the machine to track each time one of these animations ends to start a rebounding animation for the given shape.

AExtensionalTag animationsFillRed = new AExtensionalTag();

CStateMachine smBall = new CStateMachine() {
    ReboundingBall animRebound;
    public State idle = new State() {
        Transition prepareBall = new Press(BUTTON1) {
            public void action() {
                CEllipse newBall = newEllipse(getPoint().getX(), getPoint().getY(), 20, 20);
                AnimationFillPaint fillRed = new AnimationFillPaint(Color.RED);
                fillRed.setLapDuration(2000).setNbLaps(1).addTag(animationsFillRed);
                newBall.animate(fillRed);
            }
        };
        Transition ballReady = new AnimationStopped(animationsFillRed) {
            public void action() {
                animRebound = new ReboundingBall(0, 10, 400, 300);
                animRebound.setAnimatedElement(getAnimation().getAnimated()).start();
            }
        };
    };
};
		
smBall.attachTo(canvas);	

Failed to load applet. No Java 2 SDK, Standard Edition v 1.5.0 support for applet.

smClasses.jpg

See applet sources.