[trident~source:107] Wiki pages backup
- From: kirillg@kenai.com
- To: commits@trident.kenai.com
- Subject: [trident~source:107] Wiki pages backup
- Date: Mon, 8 Mar 2010 05:16:28 +0000
Project: trident
Repository: source
Revision: 107
Author: kirillg
Date: 2010-03-08 05:16:25 UTC
Link:
Log Message:
------------
Wiki pages backup
Revisions:
----------
107
Added Paths:
------------
www/wiki-backup/KeyFrameOverview.wiki.txt
www/wiki-backup/SimpleSwingExample.wiki.txt
www/wiki-backup/ease-40.png
www/wiki-backup/ParallelSWTTimelines.wiki.txt
www/wiki-backup/UIToolkitSupport.wiki.txt
www/wiki-backup/TimelineOverview.wiki.txt
www/wiki-backup/SimpleTimelineScenario.wiki.txt
www/wiki-backup/CustomUIToolkitHandlers.wiki.txt
www/wiki-backup
www/wiki-backup/ParallelSwingTimelines.wiki.txt
www/wiki-backup/TimelineLifecycle.wiki.txt
www/wiki-backup/CustomPropertyInterpolators.wiki.txt
www/wiki-backup/SimpleSWTExample.wiki.txt
www/wiki-backup/ExtensionPoints.wiki.txt
www/wiki-backup/TimelineScenarioIntroduction.wiki.txt
www/wiki-backup/ease-80.png
www/wiki-backup/progressindication.png
www/wiki-backup/CustomPulseSource.wiki.txt
www/wiki-backup/Home.wiki.txt
www/wiki-backup/TimelineInterpolatingFields.wiki.txt
www/wiki-backup/KeyFrameExample.wiki.txt
www/wiki-backup/TimelineAdditionalConfiguration.wiki.txt
Diffs:
------
Index: www/wiki-backup/CustomUIToolkitHandlers.wiki.txt
===================================================================
--- www/wiki-backup/CustomUIToolkitHandlers.wiki.txt (revision 0)
+++ www/wiki-backup/CustomUIToolkitHandlers.wiki.txt (revision 107)
@@ -0,0 +1,84 @@
+== UI toolkit handlers ==
+
+Graphical applications are a natural fit for animations, and Trident core
has built-in support for Swing and SWT. [[UIToolkitSupport|This support]]
covers threading rules, custom property interpolators and repaint timelines.
Application code that needs to support additional Java-based UI toolkits
should register a custom UI toolkit handler.
+
+Most modern UI toolkits have threading rules that the applications must
respect in order to prevent application freeze and visual artifacts. The
threading rules for both Swing and SWT specify that the UI-related operations
must be done on a special UI thread, and the methods in the
<code>org.pushingpixels.trident.UIToolkitHandler</code> are used to determine
the relevance of these threading rules. The <code>UIToolkitHandler</code>
interface is illustrated by the core support for SWT:
+
+<pre name="java">
+public class SWTToolkitHandler implements UIToolkitHandler {
+ @Override
+ public boolean isHandlerFor(Object mainTimelineObject) {
+ return (mainTimelineObject instanceof Widget);
+ }
+
+ @Override
+ public boolean isInReadyState(Object mainTimelineObject) {
+ return !((Widget) mainTimelineObject).isDisposed();
+ }
+
+ @Override
+ public void runOnUIThread(Runnable runnable) {
+ Display.getDefault().asyncExec(runnable);
+ }
+}
+</pre>
+
+This is a very simple implementation of a UI toolkit handler that respects
the relevant threading rules:
+* The <code>isHandlerFor</code> associates this handler with all SWT widgets
+* The <code>isInReadyState</code> marks disposed widgets to skip the
property interpolation / callback invocations
+* The <code>runOnUIThread</code> runs the UI related logic on the SWT thread
+
+== Registering custom UI toolkit handlers ==
+
+Trident provides two ways to register custom UI toolkit handlers -
customization APIs and plugins.
+
+The <code>TridentConfig</code> class has the following APIs to work with UI
toolkit handlers:
+
+* addUIToolkitHandler(UIToolkitHandler) - registers the UI toolkit handler
+* removeUIToolkitHandler(UIToolkitHandler) - unregisters the UI toolkit
handler
+* getUIToolkitHandlers() - retrieves an unmodifiable collection of all
registered (core and custom) UI toolkit handlers
+
+The '''UIToolkitHandler''' entries in the plugin descriptor files allow
application code to support additional Java-based UI toolkits. The value
associated with this key must be the fully qualified class name of an
application class that implements the
<code>org.pushingpixels.trident.UIToolkitHandler</code> interface.
+
+== Respecting the threading rules ==
+
+The <code>UIToolkitHandler.isHandlerFor(Object)</code> is used to determine
whether the main timeline object is a component / widget for the specific UI
toolkit. At runtime, all fields registered with the
<code>Timeline.addPropertyToInterpolate</code> methods will be changed on the
UI thread using the <code>UIToolkitHandler.runOnUIThread</code> method.
+
+In the [[SimpleSwingExample|simple Swing example]] that interpolates the
foreground color of a button on mouse rollover, the timeline is configured as
+
+<pre name="java">
+ Timeline rolloverTimeline = new Timeline(button);
+ rolloverTimeline.addPropertyToInterpolate("foreground", Color.blue,
+ Color.red);
+</pre>
+
+If you put a breakpoint in the <code>JComponent.setForeground(Color)</code>
- which is called on every timeline pulse - you will see that it is called on
the Swing Event Dispatch Thread. Internally, this is what happens:
+
+* When the timeline is created, all registered UI toolkit handlers are asked
whether they are handlers for the specified object
+* The <code>org.pushingpixels.trident.swing.SwingToolkitHandler</code>
registered in the core library returns <code>true</code> for the button
object in its <code>isHandlerFor(Object)</code>
+* On every timeline pulse, a <code>Runnable</code> object is created
internally. The <code>run()</code> method calls the setters for all
registered fields - using the <code>PropertyInterpolator.interpolate</code>
method of the matching [[PropertyInterpolatorPlugin|property interpolator]]
+* This <code>Runnable</code> is passed to the
<code>UIToolkitHandler.runOnUIThread</code> method of the matching UI toolkit
handler.
+
+And this is how <code>SwingToolkitHandler.runOnUIThread()</code> is
implemented:
+
+<pre name="java">
+ @Override
+ public void runOnUIThread(Runnable runnable) {
+ if (SwingUtilities.isEventDispatchThread())
+ runnable.run();
+ else
+ SwingUtilities.invokeLater(runnable);
+ }
+</pre>
+
+== Running custom application code on UI thread ==
+
+The flow described above works for the fields registered with the
<code>Timeline.addPropertyToInterpolate</code> methods. What about the custom
[[TimelineLifecycle|application callbacks]] registered with the
<code>Timeline.addCallback()</code>? If the callback methods need to respect
the UI threading rules of the matching toolkit, the
<code>TimelineCallback</code> implementation class needs to be tagged with
the <code>org.pushingpixels.trident.callback.RunOnUIThread</code> annotation.
+
+Callback implementations marked with this annotation will have both
<code>onTimelineStateChanged</code> and <code>onTimelinePulse</code> invoked
on the UI thread, making it safe to query and change the UI. The
<code>UIThreadTimelineCallbackAdapter</code> is a core adapter class that is
marked with this annotation.
+
+== Querying the readiness of the timeline object ==
+
+The <code>isInReadyState(Object)</code> is the third and final method in the
<code>UIToolkitHandler</code> interface. After the specific UI toolkit
handler has declared that it will handle the main object of the specific
timeline (by returning <code>true</code> from the
<code>isHandlerFor(Object)</code> method), it will be used to interpolate the
registered fields and run the registered callbacks. However, some UI toolkits
may impose additional restrictions on when the UI object is ready to be
queried / changed.
+
+For example, once an SWT control is disposed, it will throw an
<code>SWTException</code> in the <code>setForeground</code> method. So, if
the application code is running a slow animation that changes the foreground
color of a button, and the application window containing this button is
disposed in the meantime, the call to <code>setForeground</code> should be
skipped.
Index: www/wiki-backup/ease-40.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: www/wiki-backup/ease-40.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Index: www/wiki-backup/SimpleSWTExample.wiki.txt
===================================================================
--- www/wiki-backup/SimpleSWTExample.wiki.txt (revision 0)
+++ www/wiki-backup/SimpleSWTExample.wiki.txt (revision 107)
@@ -0,0 +1,68 @@
+== Interpolating foreground color of an SWT radio button ==
+The following example shows how to smoothly change the foreground color of
an SWT radio button on mouse rollover. This example showcases a few Trident
utilities that simplify animations on SWT controls.
+
+<pre name="java">
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseTrackAdapter;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.*;
+import org.pushingpixels.trident.Timeline;
+
+public class ButtonFg {
+ public static void main(String[] args) {
+ Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setSize(300, 200);
+ GridLayout layout = new GridLayout();
+ shell.setLayout(layout);
+
+ Button button = new Button(shell, SWT.RADIO);
+ GridData gridData = new GridData(GridData.CENTER,
GridData.CENTER,
+ true, false);
+ button.setLayoutData(gridData);
+
+ button.setText("sample");
+
+ Color blue = display.getSystemColor(SWT.COLOR_BLUE);
+ Color red = display.getSystemColor(SWT.COLOR_RED);
+ button.setForeground(blue);
+
+ final Timeline rolloverTimeline = new Timeline(button);
+ rolloverTimeline.addPropertyToInterpolate("foreground", blue,
red);
+ rolloverTimeline.setDuration(2500);
+ button.addMouseTrackListener(new MouseTrackAdapter() {
+ @Override
+ public void mouseEnter(MouseEvent e) {
+ rolloverTimeline.play();
+ }
+
+ @Override
+ public void mouseExit(MouseEvent e) {
+ rolloverTimeline.playReverse();
+ }
+ });
+
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ display.dispose();
+ }
+}
+</pre>
+
+* Lines 18-21 create a <code>Button</code> and add it to the center of the
frame (setting the <code>GridLayout</code>).
+* Line 27 sets the foreground of the radio button to blue
+* Line 29 creates a new timeline associated with this button.
+* Line 30 specifies that the timeline interpolates the button foreground
color between blue and red.
+* Lines 32-42 add a mouse track listener to the button. When the mouse
enters the button area, the timeline is played. When the mouse exits the
button area, the timeline is played in reverse.
+
+This example shows how the <code>Control.setForeground(Color)</code> method
is used together with the built in property interpolator for the
<code>org.eclipse.swt.graphics.Color</code> class to run the timeline that
interpolates the foreground color of an SWT radio button. Note that since the
<code>Control.setForeground(Color)</code> also repaints the button, there is
no need to explicitly repaint it on every timeline pulse.
+
+If you debug this application and put a breakpoint in the
<code>Control.setForeground(Color)</code> method, you will see that it is
called on the
[http://help.eclipse.org/stable/index.jsp?topic=/org.eclipse.platform.doc.isv/guide/swt_threading.htm
SWT Thread]. This is a built-in capability of the Trident core. It
recognizes that the timeline is associated with a SWT component, and calls
the setter method (during the timeline pulses) on the SWT thread.
+
+Finally, since we are using the <code>Timeline.play()</code> and
<code>Timeline.playReverse()</code> methods, the interpolation can be
reversed in the middle if the user moves the mouse quickly. The rollover
timeline in our example takes 2.5 seconds to complete. Suppose the user moves
the mouse over the button, and then after one second moves the mouse away.
The call to <code>playReverse</code> detects that this very timeline
[[TimelineLifecycle|is already playing]], and starts playing it in reverse
from its '''current''' position.
Index: www/wiki-backup/CustomPulseSource.wiki.txt
===================================================================
--- www/wiki-backup/CustomPulseSource.wiki.txt (revision 0)
+++ www/wiki-backup/CustomPulseSource.wiki.txt (revision 107)
@@ -0,0 +1,66 @@
+== Timeline pulse source ==
+By default, Trident timelines are driven by a dedicated thread that wakes up
every 40ms and updates all the timelines. When the CPU is not heavily used
this results in 25 frames-per-second refresh rate for Trident-driven UI
animations - consistent with the frame rate of theatrical films and
non-interlaced PAL television standard.
+
+Applications that require custom pulse behavior - higher frame rate, lower
frame rate or dynamic frame rate - should use the
<code>TridentConfig.setPulseSource(PulseSource)</code> API. The
<code>PulseSource</code> interface is:
+
+<pre name="java">
+public interface PulseSource {
+ public void waitUntilNextPulse();
+}
+</pre>
+
+The implementation of <code>waitUntilNextPulse()</code> is expected to be a
blocking call that returns on the next target pulse.
+
+== Sample usage ==
+
+The following class installs a custom pulse source that fires timeline
pulses every 100 milliseconds:
+
+<pre name="java">
+public class CustomPulseSource {
+ private float value;
+
+ public void setValue(float newValue) {
+ SimpleDateFormat sdf = new SimpleDateFormat("mm:SSS");
+ System.out.println(sdf.format(new Date()) + " : " + this.value + " -> "
+ + newValue);
+ this.value = newValue;
+ }
+
+ public static void main(String[] args) {
+ TridentConfig.getInstance().setPulseSource(
+ new TridentConfig.PulseSource() {
+ @Override
+ public void waitUntilNextPulse() {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ });
+ CustomPulseSource helloWorld = new CustomPulseSource();
+ Timeline timeline = new Timeline(helloWorld);
+ timeline.addPropertyToInterpolate("value", 0.0f, 1.0f);
+ timeline.play();
+
+ try {
+ Thread.sleep(3000);
+ } catch (Exception exc) {
+ }
+ }
+}
+</pre>
+
+This example uses the blocking <code>Thread.sleep()</code> API. Sample
output of running this class is:
+
+<pre>
+40:362 : 0.0 -> 0.0
+40:362 : 0.0 -> 0.0
+40:456 : 0.0 -> 0.2
+40:550 : 0.2 -> 0.746
+40:659 : 0.746 -> 0.946
+40:753 : 0.946 -> 1.0
+40:753 : 1.0 -> 1.0
+</pre>
+
+Discarding the first and last lines - that correspond to the start and the
end of the timeline - the custom pulse source is indeed firing on average
every 100 ms as expected. Applications that have a higher-precision timer
implementation - perhaps using native APIs - can provide the matching
<code>PulseSource</code> implementation to achieve a higher pulse rate.
\ No newline at end of file
Index: www/wiki-backup/ParallelSWTTimelines.wiki.txt
===================================================================
--- www/wiki-backup/ParallelSWTTimelines.wiki.txt (revision 0)
+++ www/wiki-backup/ParallelSWTTimelines.wiki.txt (revision 107)
@@ -0,0 +1,149 @@
+== Multiple timelines in SWT applications ==
+
+Trident supports running multiple independent timelines at the same time.
This page shows the SWT application behind [http://vimeo.com/3404285 this
video], where every cell rollover is implemented as a separate timeline.
+
+We start with a class that implements a specific grid cell:
+
+<pre name="java">
+ public static class SnakePanelRectangle {
+ private Color backgroundColor;
+
+ private boolean isRollover;
+
+ private Timeline rolloverTimeline;
+
+ public SnakePanelRectangle() {
+ this.backgroundColor =
Display.getDefault().getSystemColor(
+ SWT.COLOR_BLACK);
+ this.isRollover = false;
+
+ this.rolloverTimeline = new Timeline(this);
+
this.rolloverTimeline.addPropertyToInterpolate("backgroundColor",
+
Display.getDefault().getSystemColor(SWT.COLOR_YELLOW),
+
Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
+ this.rolloverTimeline.setDuration(2500);
+ }
+
+ public void setRollover(boolean isRollover) {
+ if (this.isRollover == isRollover)
+ return;
+ this.isRollover = isRollover;
+ if (this.isRollover) {
+ this.rolloverTimeline.replay();
+ }
+ }
+
+ public void setBackgroundColor(Color backgroundColor) {
+ this.backgroundColor = backgroundColor;
+ }
+
+ public Color getBackgroundColor() {
+ return backgroundColor;
+ }
+ }
+</pre>
+
+A few major points in this class:
+* The default background color of a cell is black (lines 9-10).
+* The rollover timeline interpolates the background color from yellow to
black over a period of 2.5 seconds (lines 13-17).
+* The rollover timeline is replayed when <code>setRollover</code> is called
with <code>true</code>. This restarts the timeline to interpolate the
foreground color from yellow.
+
+The next class implements a cell grid, tracing the mouse events and
dispatching the rollover events to the relevant cells:
+
+<pre name="java">
+ private static class SnakePanel extends Canvas {
+
+ private SnakePanelRectangle[][] grid;
+
+ private int ROWS = 10;
+
+ private int COLUMNS = 20;
+
+ private int DIM = 20;
+
+ public SnakePanel(Composite parent) {
+ super(parent, SWT.DOUBLE_BUFFERED);
+
+ this.grid = new SnakePanelRectangle[COLUMNS][ROWS];
+ for (int i = 0; i < COLUMNS; i++) {
+ for (int j = 0; j < ROWS; j++) {
+ this.grid[i][j] = new
SnakePanelRectangle();
+ }
+ }
+
+ Timeline repaint = new SWTRepaintTimeline(this);
+ repaint.playLoop(RepeatBehavior.LOOP);
+
+ this.addMouseMoveListener(new MouseMoveListener() {
+ int rowOld = -1;
+ int colOld = -1;
+
+ @Override
+ public void mouseMove(MouseEvent e) {
+ int x = e.x;
+ int y = e.y;
+
+ int column = x / (DIM + 1);
+ int row = y / (DIM + 1);
+
+ if ((column >= COLUMNS) || (row >=
ROWS))
+ return;
+
+ if ((column != colOld) || (row !=
rowOld)) {
+ if ((colOld >= 0) && (rowOld
>= 0))
+
grid[colOld][rowOld].setRollover(false);
+
grid[column][row].setRollover(true);
+ }
+ colOld = column;
+ rowOld = row;
+ }
+ });
+
+ this.addPaintListener(new PaintListener() {
+ @Override
+ public void paintControl(PaintEvent e) {
+ GC gc = e.gc;
+
gc.setBackground(e.display.getSystemColor(SWT.COLOR_BLACK));
+ gc.fillRectangle(e.x, e.y, e.width,
e.height);
+
+ for (int i = 0; i < COLUMNS; i++) {
+ for (int j = 0; j < ROWS;
j++) {
+ SnakePanelRectangle
rect = grid[i][j];
+ Color backgr =
rect.getBackgroundColor();
+
gc.setBackground(backgr);
+ gc.fillRectangle(i *
(DIM + 1), j * (DIM + 1), DIM,
+ DIM);
+ }
+ }
+ }
+ });
+ }
+ }
+</pre>
+
+A few major points in this class:
+* A special type of timeline is created and played in a loop - lines 21-22.
In this example, each cell rollover timeline changes the background color of
that cell, but '''does not''' cause the repaint. Instead, we have a "master"
repaint timeline that runs in a loop and causes the repaint of the entire
grid panel.
+* The mouse move listener (lines 24-47) tracks the mouse location, calling
the <code>setRollover</code> method on relevant cells. Since each cell
rollover timeline runs for 2.5 seconds, quick mouse moves will result in
multiple timelines running in parallel.
+* The painting of each cell respects the current background color of that
cell - lines 59-62.
+
+Finally, the main method that creates a host frame and adds the cell grid
panel to it:
+
+<pre name="java">
+ public static void main(String[] args) {
+ Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setSize(430, 240);
+ shell.setText("SWT Snake");
+ FillLayout layout = new FillLayout();
+ shell.setLayout(layout);
+
+ SnakePanel snake = new SnakePanel(shell);
+
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ display.dispose();
+ }
+</pre>
Index: www/wiki-backup/SimpleTimelineScenario.wiki.txt
===================================================================
--- www/wiki-backup/SimpleTimelineScenario.wiki.txt (revision 0)
+++ www/wiki-backup/SimpleTimelineScenario.wiki.txt (revision 107)
@@ -0,0 +1,303 @@
+== Simple Swing timeline scenario ==
+
+The following example shows a Swing application with a simple
[[TimelineScenarioIntroduction|timeline scenario]] that launches five
parallel timelines. It shows the code behind [http://vimeo.com/3401957 this
video], where every volley is a separate timeline, and all currently playing
volleys are part of the same timeline scenario.
+
+In the code, there are three "hierarchy" levels of fireworks:
+* The entire fireworks display - this is a timeline scenario that consists
of five volley explosions.
+* The volley explosion implemented in <code>VolleyExplosion</code> class -
this is a collection of single explosions that have the same color and
originate from the same explosion center point.
+* The single explosion implemented in <code>SingleExplosion</code> class -
this is a fading circle that represents a single "leaf" part of the volley
explosion.
+
+The code behind the single explosion is quite simple:
+
+<pre name="java">
+ public class SingleExplosion {
+ float x;
+
+ float y;
+
+ float radius;
+
+ float opacity;
+
+ Color color;
+
+ public SingleExplosion(Color color, float x, float y, float
radius) {
+ this.color = color;
+ this.x = x;
+ this.y = y;
+ this.radius = radius;
+ this.opacity = 1.0f;
+ }
+
+ public void setX(float x) {
+ this.x = x;
+ }
+
+ public void setY(float y) {
+ this.y = y;
+ }
+
+ public void setRadius(float radius) {
+ this.radius = radius;
+ }
+
+ public void setOpacity(float opacity) {
+ this.opacity = opacity;
+ }
+
+ public void paint(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
g2d.setComposite(AlphaComposite.SrcOver.derive(this.opacity));
+ g2d.setColor(this.color);
+ g2d.fill(new Ellipse2D.Float(this.x - this.radius,
this.y
+ - this.radius, 2 * radius, 2 *
radius));
+ g2d.dispose();
+ }
+ }
+</pre>
+
+It has four fields (lines 2-10) that specify its location, size, opacity and
color. Each field except the color has a public setter that is used in the
timeline created in the parent volley explosion (lines 20-34). Finally, it
has a custom painting implementation that paints the graphical representation
of the single volley (lines 36-45).
+
+The volley explosion is implemented by the following class:
+<pre name="java">
+ public class VolleyExplosion {
+ private int x;
+
+ private int y;
+
+ private Color color;
+
+ private Set<SingleExplosion> circles;
+
+ public VolleyExplosion(int x, int y, Color color) {
+ this.x = x;
+ this.y = y;
+ this.color = color;
+ this.circles = new HashSet<SingleExplosion>();
+ }
+
+ public TimelineScenario getExplosionScenario() {
+ TimelineScenario scenario = new
TimelineScenario.Parallel();
+
+ int duration = 1000 + (int) (1000 * Math.random());
+ for (int i = 0; i < 18; i++) {
+ float dist = (float) (50 + 10 *
Math.random());
+ float radius = (float) (2 + 2 *
Math.random());
+ for (float delta = 0.6f; delta <= 1.0f; delta
+= 0.2f) {
+ float circleRadius = radius * delta;
+
+ double degrees = 20.0 * (i +
Math.random());
+ float radians = (float) (2.0 *
Math.PI * degrees / 360.0);
+
+ float initDist = delta * dist / 10.0f;
+ float finalDist = delta * dist;
+ float initX = (float) (this.x +
initDist
+ * Math.cos(radians));
+ float initY = (float) (this.y +
initDist
+ * Math.sin(radians));
+ float finalX = (float) (this.x +
finalDist
+ * Math.cos(radians));
+ float finalY = (float) (this.y +
finalDist
+ * Math.sin(radians));
+
+ SingleExplosion circle = new
SingleExplosion(this.color,
+ initX, initY,
circleRadius);
+ Timeline timeline = new
Timeline(circle);
+
timeline.addPropertyToInterpolate("x", initX, finalX);
+
timeline.addPropertyToInterpolate("y", initY, finalY);
+
timeline.addPropertyToInterpolate("opacity", 1.0f, 0.0f);
+ timeline.setDuration(duration - 200
+ + (int) (400 *
Math.random()));
+ timeline.setEase(new Spline(0.4f));
+
+ synchronized (this.circles) {
+ circles.add(circle);
+ }
+ scenario.addScenarioActor(timeline);
+ }
+ }
+
+ return scenario;
+ }
+
+ public void paint(Graphics g) {
+ synchronized (this.circles) {
+ for (SingleExplosion circle : this.circles) {
+ circle.paint(g);
+ }
+ }
+ }
+ }
+</pre>
+
+A quick walkthrough:
+* Lines 2-6 specify the fields that store the center and the color of the
explosion.
+* Line 8 specifies the set that stores all the single explosions of this
volley explosion.
+* Lines 17-59 return a timeline scenario that implements this volley
explosion:
+** Line 18 creates a parallel timeline. '''Each''' single explosion is
implemented as a separate timeline.
+** Line 20 computes a random duration for this scenario.
+** Lines 21 and 24 create 54 single explosions.
+** Lines 21, 27 and 28 create single explosions at almost evenly distributed
angles (every 20 degrees).
+** Lines 22, 24 and 30-39 create single explosions at almost evenly
distributed distance from the center (three for each angle).
+** Lines 41-42 create the <code>SingleExplosion</code> object.
+** Lines 43-49 create the timeline that interpolates the relevant properties
of that object. Each timeline has a random duration.
+** Line 54 adds the created timeline to the scenario.
+* The scenario returned on line 58 has 54 different timelines, one for each
single explosion.
+* Lines 61-67 paint all the single explosions of this volley explosion.
+
+Now we get to the main application class. It implements the following
functionality:
+* Playing five explosion volleys (five timeline scenarios).
+* Waiting for all five to be done.
+* Playing another five - repeating the previous two steps.
+* Listening to the mouse events, suspending the currently playing scenarios
on mouse press, and resuming them on mouse release.
+
+The code starts by declaring the relevant data structures:
+<pre name="java">
+public class Fireworks extends JFrame {
+ private Set<VolleyExplosion> volleys;
+
+ private Map<VolleyExplosion, TimelineScenario> volleyScenarios;
+
+ private JPanel mainPanel;
+</pre>
+
+Here is the constructor of this class:
+<pre name="java">
+public Fireworks() {
+ super("Swing Fireworks");
+
+ this.mainPanel = new JPanel() {
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ synchronized (volleys) {
+ for (VolleyExplosion exp : volleys)
+ exp.paint(g);
+ }
+ }
+ };
+ this.mainPanel.setBackground(Color.black);
+ this.mainPanel.setPreferredSize(new Dimension(480, 320));
+
+ Timeline repaint = new SwingRepaintTimeline(this);
+ repaint.playLoop(RepeatBehavior.LOOP);
+
+ this.volleys = new HashSet<VolleyExplosion>();
+ this.volleyScenarios = new HashMap<VolleyExplosion, TimelineScenario>();
+
+ this.mainPanel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ synchronized (volleys) {
+ for (TimelineScenario scenario : volleyScenarios.values())
+ scenario.suspend();
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ synchronized (volleys) {
+ for (TimelineScenario scenario : volleyScenarios.values())
+ scenario.resume();
+ }
+ }
+ });
+
+ mainPanel.addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ if ((mainPanel.getWidth() == 0) || (mainPanel.getHeight() == 0))
+ return;
+ new Thread() {
+ @Override
+ public void run() {
+ while (true) {
+ addExplosions(5);
+ }
+ }
+ }.start();
+ }
+ });
+
+ this.add(mainPanel);
+ this.pack();
+ this.setLocationRelativeTo(null);
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+}
+</pre>
+
+* Lines 4-15 create a <code>JPanel</code> that paints all currently playing
volley explosions on black background.
+* Lines 17-18 create a looping timeline that repaints the contents of this
application.
+* Lines 20-21 create the data structures tracking the currently playing
explosions.
+* Lines 23-39 add the mouse listener that suspends the currently playing
scenarios on mouse press and resumes them on mouse release.
+* Lines 41-55 register a listener that checks the size of the panel. When
the panel has a non-zero size, the listener creates and runs a thread that
adds five explosions in an infinite loop (see the explanation of
<code>addExplosions</code> below)
+* Lines 57-60 configure the application hierarchy and location
+
+Here is the method that makes sure that the volley explosions are run in
batches of 5, even when they have different durations:
+
+<pre name="java">
+ private void addExplosions(int count) {
+ final CountDownLatch latch = new CountDownLatch(count);
+
+ for (int i = 0; i < count; i++) {
+ int r = (int) (255 * Math.random());
+ int g = (int) (100 + 155 * Math.random());
+ int b = (int) (50 + 205 * Math.random());
+ Color color = new Color(r, g, b);
+
+ int x = 60 + (int) ((mainPanel.getWidth() - 120) *
Math.random());
+ int y = 60 + (int) ((mainPanel.getHeight() - 120) *
Math.random());
+ final VolleyExplosion exp = new VolleyExplosion(x, y,
color);
+ synchronized (volleys) {
+ volleys.add(exp);
+ TimelineScenario scenario =
exp.getExplosionScenario();
+ scenario.addCallback(new
TimelineScenarioCallback() {
+ @Override
+ public void onTimelineScenarioDone() {
+ synchronized (volleys) {
+ volleys.remove(exp);
+
volleyScenarios.remove(exp);
+ latch.countDown();
+ }
+ }
+ });
+ volleyScenarios.put(exp, scenario);
+ scenario.play();
+ }
+ }
+
+ try {
+ latch.await();
+ } catch (Exception exc) {
+ }
+ }
+</pre>
+
+* Line 2 creates a <code>CountDownLatch</code> that will be used to wait
until all timeline scenarios that run the volley explosions are done
+* Lines 5-8 compute a random color for each one of the volley explosions
+* Lines 10-11 compute a random center location for each one of the volley
explosions
+* Line 12 creates a new <code>VolleyExplosion</code> object
+* Line 15 creates the timeline scenario that corresponds to this object
+* Lines 16-25 add a callback that notifies the count down latch when the
timeline scenario is done
+* Line 27 plays the timeline scenario
+* Lines 31-34 wait on the count down latch - until '''all''' timeline
scenarios are done
+
+And finally, the main method to launch the fireworks:
+
+<pre name="java">
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ new Fireworks().setVisible(true);
+ }
+ });
+ }
+</pre>
+
+----
+<center>Click below for the WebStart demo</center>
+<center><a
href="https://kenai.com/svn/trident~source/www/webstart/Fireworks.jnlp">[[image:webstart.png]]</a></center>
Index: www/wiki-backup/ease-80.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: www/wiki-backup/ease-80.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Index: www/wiki-backup/CustomPropertyInterpolators.wiki.txt
===================================================================
--- www/wiki-backup/CustomPropertyInterpolators.wiki.txt (revision 0)
+++ www/wiki-backup/CustomPropertyInterpolators.wiki.txt (revision 107)
@@ -0,0 +1,185 @@
+== Property interpolators ==
+
+Trident supports interpolation of primitive values - such as integers,
floats and point / color / rectangle classes of supported
[[UIToolkitSupport|UI toolkits]]. Application code that needs to interpolate
fields of these types does not need to explicitly state how the field value
is interpolated between the start / current and end value. For other field
types the application code can either register custom property interpolators,
or [[TimelineInterpolatingFields|explicitly state]] the property interpolator
to be used for computing the field value.
+
+For both cases the application code needs to provide one or more
implementations of the
<code>org.pushingpixels.trident.interpolator.PropertyInterpolator</code>
interface. This interface has two methods.
+
+The <code>public Class getBasePropertyClass()</code> is used to choose the
property interpolator in the <code>Timeline.addPropertyToInterpolate(String,
Object, Object)</code>. Internally, all registered property interpolators are
queried to check whether they support the specified ''from'' and ''to''
values using the <code>Class.isAssignableFrom(Class)</code>. The '''first'''
property interpolator that has a match for both values will be used.
+
+For example, the <code>PointInterpolator</code> in the core AWT property
interpolator source (<code>AWTPropertyInterpolators</code> class) has the
following implementation of this method:
+
+<pre name="java">
+ @Override
+ public Class getBasePropertyClass() {
+ return Point.class;
+ }
+</pre>
+
+The <code>public T interpolate(T from, T to, float timelinePosition)</code>
is used to compute the interpolated value during the current timeline pulse.
For example, the <code>PointInterpolator</code> in the core AWT property
interpolator source (<code>AWTPropertyInterpolators</code> class) has the
following implementation of this method:
+
+<pre name="java">
+ public Point interpolate(Point from, Point to, float
timelinePosition) {
+ int x = from.x + (int) (timelinePosition * (to.x - from.x));
+ int y = from.y + (int) (timelinePosition * (to.y - from.y));
+ return new Point(x, y);
+ }
+</pre>
+
+== Registering custom property interpolators ==
+
+<code>TimelinePropertyBuilder.interpolatedWith(PropertyInterpolator)</code>
API can be used to explicitly state the property interpolator to be used for
the specific property. However, using this API may lead to a lot of
boilerplate code in applications that have multiple animations of fields of
the same custom type. In such cases it is recommended to register custom
property interpolators and have Trident automatically pick up the matching
interpolator at runtime. Trident provides two ways to register custom
interpolators - customization APIs and plugins.
+
+The <code>TridentConfig</code> class has the following APIs to work with
property interpolators:
+
+* <code>addPropertyInterpolatorSource(PropertyInterpolatorSource)</code> -
registers all the property interpolators provided by this source
+* <code>addPropertyInterpolator(PropertyInterpolator)</code> - registers the
property interpolator
+* <code>removePropertyInterpolator(PropertyInterpolator)</code> -
unregisters the property interpolator
+* <code>getPropertyInterpolators()</code> - retrieves an unmodifiable list
of all registered (core and custom) property interpolators
+* <code>getPropertyInterpolator(Object...)</code> - retrieves the first
property interpolator that matches all the passed objects, or
<code>null</code> if no match is found
+
+The '''PropertyInterpolatorSource''' entries in the [[PluginOverview|plugin
descriptor]] files allow application code to provide property interpolators
for custom application classes. The value associated with this key must be
the fully qualified class name of an application class that implements the
<code>org.pushingpixels.trident.interpolator.PropertyInterpolatorSource</code>
interface.
+
+This interface has one method - <code>public Set<PropertyInterpolator>
getPropertyInterpolators()</code> - which returns a set of custom property
interpolators. Custom property interpolators can be used in two ways:
+
+* The <code>Timeline.addPropertyToInterpolate(String, Object, Object)</code>
API that will choose the property interpolator that matches the types of the
''from'' and ''to'' values
+* Use a [[TimelineInterpolatingFields|timeline property builder]] and the
<code>TimelinePropertyBuilder.interpolatedWith()</code> API. The
<code>Timeline.addPropertyToInterpolate(TimelinePropertyBuilder)</code> API
will use the specified property interpolator
+
+== Bringing it together ==
+
+Let's look at the following Swing example that moves a red rectangle in an
infinite loop:
+
+<pre name="java">
+public class MovingRectangle extends JFrame {
+ public static class MyRectangle {
+ private Point corner = new Point(0, 0);
+
+ public void setCorner(Point corner) {
+ this.corner = corner;
+ }
+
+ void paint(Graphics g) {
+ g.setColor(Color.red);
+ g.fillRect(this.corner.x, this.corner.y, 100, 100);
+ }
+ }
+
+ public MovingRectangle() {
+ super("Moving rectangle");
+ final MyRectangle rectangle = new MyRectangle();
+ JPanel panel = new JPanel() {
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ rectangle.paint(g);
+ }
+ };
+
+ Timeline move = new Timeline(rectangle);
+ move.addPropertyToInterpolate("corner", new Point(0, 0), new Point(100,
+ 80));
+ move.setDuration(2000);
+ move.playLoop(RepeatBehavior.REVERSE);
+
+ Timeline repaint = new SwingRepaintTimeline(panel);
+ repaint.playLoop(RepeatBehavior.LOOP);
+
+ this.add(panel);
+ this.setSize(200, 200);
+ this.setLocationRelativeTo(null);
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ }
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ new MovingRectangle().setVisible(true);
+ }
+ });
+ }
+}
+</pre>
+
+What happens in lines 35-36? Internally, the Trident core looks at all
available property interpolators and finds that the
<code>AWTPropertyInterpolators.PointInterpolator</code> is the best match for
the passed values (which are both <code>java.awt.Point</code>s). Then, at
every pulse of the <code>move</code> timeline, the
<code>MyRectangle.setCorner(Point)</code> is called.
+
+Note that the application code '''did not''' explicitly specify which
property interpolator should be used. The following class illustrates the
usage of <code>TridentConfig.addPropertyInterpolator</code> API:
+
+<pre name="java">
+public class CustomPropertyInterpolatorSource extends JFrame {
+ private Ellipse2D ellipse;
+
+ private static class Ellipse2DPropertyInterpolator implements
+ PropertyInterpolator<Ellipse2D> {
+ public Class getBasePropertyClass() {
+ return Ellipse2D.class;
+ }
+
+ @Override
+ public Ellipse2D interpolate(Ellipse2D from, Ellipse2D to,
+ float timelinePosition) {
+ double x = from.getX() + timelinePosition
+ * (to.getX() - from.getX());
+ double y = from.getY() + timelinePosition
+ * (to.getY() - from.getY());
+ double w = from.getWidth() + timelinePosition
+ * (to.getWidth() - from.getWidth());
+ double h = from.getHeight() + timelinePosition
+ * (to.getHeight() - from.getHeight());
+ return new Ellipse2D.Double(x, y, w, h);
+ }
+ }
+
+ public CustomPropertyInterpolatorSource() {
+ TridentConfig.getInstance().addPropertyInterpolator(
+ new Ellipse2DPropertyInterpolator());
+
+ Ellipse2D from = new Ellipse2D.Double(10, 10, 100, 50);
+ Ellipse2D to = new Ellipse2D.Double(40, 40, 200, 120);
+ this.ellipse = (Ellipse2D) from.clone();
+ JPanel ellipsePanel = new JPanel() {
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setColor(Color.red);
+ g2d.fill(ellipse);
+
+ g2d.dispose();
+ }
+ };
+ ellipsePanel.setBackground(Color.black);
+
+ Timeline ellipseTimeline = new Timeline(this);
+ ellipseTimeline.addPropertyToInterpolate("ellipse", from, to);
+ ellipseTimeline.setEase(new Sine());
+ ellipseTimeline.setDuration(2000);
+ ellipseTimeline.playLoop(RepeatBehavior.REVERSE);
+
+ new SwingRepaintTimeline(ellipsePanel).playLoop(RepeatBehavior.LOOP);
+
+ this.add(ellipsePanel);
+
+ this.setSize(400, 300);
+ this.setLocationRelativeTo(null);
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ }
+
+ public void setEllipse(Ellipse2D ellipse) {
+ this.ellipse = ellipse;
+ }
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ new CustomPropertyInterpolatorSource().setVisible(true);
+ }
+ });
+ }
+}
+</pre>
+
+Here, the registered property interpolator is implicitly used to animate the
expanding / shrinking ellipse.
\ No newline at end of file
Index: www/wiki-backup/progressindication.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: www/wiki-backup/progressindication.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Index: www/wiki-backup/ExtensionPoints.wiki.txt
===================================================================
--- www/wiki-backup/ExtensionPoints.wiki.txt (revision 0)
+++ www/wiki-backup/ExtensionPoints.wiki.txt (revision 107)
@@ -0,0 +1,47 @@
+== Core functionality ==
+The core functionality of the Trident library can be extended to address
custom needs of the specific applications. Out of the box Trident supports:
+
+* The timeline pulses being fired by a dedicated thread that wakes up every
40ms and updates all the timelines
+* Interpolating [[TimelineInterpolatingFields|float and integer fields]] of
any Java object that provides the matching public setter methods.
+* Swing and SWT [[UIToolkitSupport|UI toolkits]], respecting the threading
rules and providing interpolators for the custom graphic classes
+
+Interested applications can use Trident plugins and Trident configuration
APIs to:
+
+* Provide a custom pulse source to drive the timeline updates
+* Provide additional property interpolators for custom application classes
+* Support additional Java-based UI toolkits
+
+== Configuration APIs ==
+
+The <code>org.pushingpixels.trident.TridentConfig</code> class contains the
published configuration APIs. The <code>TridentConfig.getInstance()</code>
API retrieves an instance of this class which can then be used for:
+
+* Setting a custom [[CustomPulseSource|pulse source]]
+* Adding, removing and querying [[CustomPropertyInterpolators|property
interpolators]]
+* Adding, removing and querying [[CustomUIToolkitHandlers|UI toolkit
handlers]]
+
+== Plugins ==
+
+A Trident plugin is specified by the
<code>META-INF/trident-plugin.properties</code> file that should be placed in
the runtime classpath. Note that you can have multiple plugins in the same
runtime environment - if each one is coming from a different classpath jar,
for example.
+
+The format of <code>trident-plugin.properties</code> is simple. Each line in
this file should be of the following format:
+<pre>
+Key=FullyQualifiedClassName
+</pre>
+
+There supported keys are:
+
+* '''PropertyInterpolatorSource''' allows specifying
[[CustomPropertyInterpolators|custom property interpolators]]
+* '''UIToolkitHandler''' allows supporting [[CustomUIToolkitHandlers|custom
UI toolkits]]
+
+The core Trident library contains a plugin that supports Swing and SWT UI
toolkits, as well as property interpolators for a few core classes. The
plugin descriptor is <code>META-INF/trident-plugin.properties</code> in the
<code>trident.jar</code>
+
+<pre>
+UIToolkitHandler=org.pushingpixels.trident.swing.SwingToolkitHandler
+PropertyInterpolatorSource=org.pushingpixels.trident.swing.AWTPropertyInterpolators
+
+UIToolkitHandler=org.pushingpixels.trident.swt.SWTToolkitHandler
+PropertyInterpolatorSource=org.pushingpixels.trident.swt.SWTPropertyInterpolators
+
+PropertyInterpolatorSource=org.pushingpixels.trident.interpolator.CorePropertyInterpolators
+</pre>
+
Index: www/wiki-backup/SimpleSwingExample.wiki.txt
===================================================================
--- www/wiki-backup/SimpleSwingExample.wiki.txt (revision 0)
+++ www/wiki-backup/SimpleSwingExample.wiki.txt (revision 107)
@@ -0,0 +1,67 @@
+== Interpolating foreground color of a Swing button ==
+The following example shows how to smoothly change the foreground color of a
Swing button on mouse rollover. This example showcases a few Trident
utilities that simplify animations on Swing controls.
+
+<pre name="java">
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.*;
+
+import org.pushingpixels.trident.Timeline;
+
+public class ButtonFg extends JFrame {
+ public ButtonFg() {
+ JButton button = new JButton("sample");
+ button.setForeground(Color.blue);
+
+ this.setLayout(new FlowLayout());
+ this.add(button);
+
+ final Timeline rolloverTimeline = new Timeline(button);
+ rolloverTimeline.addPropertyToInterpolate("foreground",
Color.blue,
+ Color.red);
+ rolloverTimeline.setDuration(2500);
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ rolloverTimeline.play();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ rolloverTimeline.playReverse();
+ }
+ });
+
+ this.setSize(400, 200);
+ this.setLocationRelativeTo(null);
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ }
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ new ButtonFg().setVisible(true);
+ }
+ });
+ }
+}
+</pre>
+
+* Lines 12-16 create a <code>JButton</code> with blue foreground color and
add it to the center of the frame (setting the <code>FlowLayout</code>).
+* Line 18 creates a new timeline associated with this button.
+* Lines 19-20 specify that the timeline interpolates the button foreground
color between blue and red.
+* Lines 22-32 add a mouse listener to the button. When the mouse enters the
button area, the timeline is played. When the mouse exits the button area,
the timeline is played in reverse.
+
+This example shows how the <code>JComponent.setForeground(Color)</code>
method is used together with the built in property interpolator for the
<code>java.awt.Color</code> class to run the timeline that interpolates the
foreground color of a Swing button. Note that since the
<code>JComponent.setForeground(Color)</code> also repaints the button, there
is no need to explicitly repaint it on every timeline pulse.
+
+If you debug this application and put a breakpoint in the
<code>JComponent.setForeground(Color)</code> method, you will see that it is
called on the
[http://www.javaworld.com/javaworld/jw-08-2007/jw-08-swingthreading.html
Event Dispatch Thread]. This is a built-in capability of the Trident core. It
recognizes that the timeline is associated with a Swing component, and calls
the setter method (during the timeline pulses) on the EDT.
+
+Finally, since we are using the <code>Timeline.play()</code> and
<code>Timeline.playReverse()</code> methods, the interpolation can be
reversed in the middle if the user moves the mouse quickly. The rollover
timeline in our example takes 2.5 seconds to complete. Suppose the user moves
the mouse over the button, and then after one second moves the mouse away.
The call to <code>playReverse</code> detects that this very timeline
[[TimelineLifecycle|is already playing]], and starts playing it in reverse
from its '''current''' position.
+
+----
+<center>Click below for the WebStart demo</center>
+<center><a
href="https://kenai.com/svn/trident~source/www/webstart/ButtonFg.jnlp">[[image:webstart.png]]</a></center>
\ No newline at end of file
Index: www/wiki-backup/ParallelSwingTimelines.wiki.txt
===================================================================
--- www/wiki-backup/ParallelSwingTimelines.wiki.txt (revision 0)
+++ www/wiki-backup/ParallelSwingTimelines.wiki.txt (revision 107)
@@ -0,0 +1,147 @@
+== Multiple timelines in Swing applications ==
+
+Trident supports running multiple independent timelines at the same time.
This page shows the Swing application behind [http://vimeo.com/3404285 this
video], where every cell rollover is implemented as a separate timeline.
+
+We start with a class that implements a specific grid cell:
+
+<pre name="java">
+ public static class SnakePanelRectangle {
+ private Color backgroundColor;
+
+ private boolean isRollover;
+
+ private Timeline rolloverTimeline;
+
+ public SnakePanelRectangle() {
+ this.backgroundColor = Color.black;
+ this.isRollover = false;
+
+ this.rolloverTimeline = new Timeline(this);
+
this.rolloverTimeline.addPropertyToInterpolate("backgroundColor",
+ Color.yellow, Color.black);
+ this.rolloverTimeline.setDuration(2500);
+ }
+
+ public void setRollover(boolean isRollover) {
+ if (this.isRollover == isRollover)
+ return;
+ this.isRollover = isRollover;
+ if (this.isRollover) {
+ this.rolloverTimeline.replay();
+ }
+ }
+
+ public void setBackgroundColor(Color backgroundColor) {
+ this.backgroundColor = backgroundColor;
+ }
+
+ public Color getBackgroundColor() {
+ return backgroundColor;
+ }
+ }
+</pre>
+
+A few major points in this class:
+* The default background color of a cell is black (line 9).
+* The rollover timeline interpolates the background color from yellow to
black over a period of 2.5 seconds (lines 12-15).
+* The rollover timeline is replayed when <code>setRollover</code> is called
with <code>true</code>. This restarts the timeline to interpolate the
foreground color from yellow.
+
+The next class implements a cell grid, tracing the mouse events and
dispatching the rollover events to the relevant cells:
+
+<pre name="java">
+ private static class SnakePanel extends JPanel {
+
+ private SnakePanelRectangle[][] grid;
+
+ private int ROWS = 10;
+
+ private int COLUMNS = 20;
+
+ private int DIM = 20;
+
+ public SnakePanel() {
+ this.grid = new SnakePanelRectangle[COLUMNS][ROWS];
+ for (int i = 0; i < COLUMNS; i++) {
+ for (int j = 0; j < ROWS; j++) {
+ this.grid[i][j] = new
SnakePanelRectangle();
+ }
+ }
+ this.setPreferredSize(new Dimension(COLUMNS * (DIM +
1), ROWS
+ * (DIM + 1)));
+
+ Timeline repaint = new SwingRepaintTimeline(this);
+ repaint.playLoop(RepeatBehavior.LOOP);
+
+ this.addMouseMotionListener(new MouseMotionAdapter() {
+ int rowOld = -1;
+ int colOld = -1;
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ int x = e.getX();
+ int y = e.getY();
+
+ int column = x / (DIM + 1);
+ int row = y / (DIM + 1);
+
+ if ((column != colOld) || (row !=
rowOld)) {
+ if ((colOld >= 0) && (rowOld
>= 0))
+
grid[colOld][rowOld].setRollover(false);
+
grid[column][row].setRollover(true);
+ }
+ colOld = column;
+ rowOld = row;
+ }
+ });
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ g2d.setColor
[truncated due to length]
|
[trident~source:107] Wiki pages backup |
kirillg | 03/08/2010 |





