Over the course of the next few days i’m going to talk about different concepts in the Trident animation library for Java applications. Part six shows simple Swing / SWT examples that highlight Trident support for Java-based UI toolkits.
Simple Swing example
The following example shows how to smoothly change the foreground color of a Swing button on mouse rollover.
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);
}
});
}
}
Here, we have a timeline that interpolates the foreground color between blue and red. The mouse listener registered on the button plays this timeline on mouse enter, and plays this timeline in reverse on mouse exit.
This example shows how the JComponent.setForeground(Color)
method is used together with the built in property interpolator for the java.awt.Color
class to run the timeline that interpolates the foreground color of a Swing button. Note that since the JComponent.setForeground(Color)
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 JComponent.setForeground(Color)
method, you will see that it is called on the 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.
Simple SWT example
The following example is the SWT version of changing the control foreground color on mouse rollover:
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();
}
}
As with Swing, the Control.setForeground(Color)
method is used together with the built in property interpolator for the org.eclipse.swt.graphics.Color
class to run the timeline that interpolates the foreground color of an SWT radio button. Note that since the Control.setForeground(Color)
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 Control.setForeground(Color)
method, you will see that it is called on the 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 both examples are using the Timeline.play()
and Timeline.playReverse()
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 playReverse
detects that this very timeline is already playing, and starts playing it in reverse from its current position.
Over the course of the next few days i’m going to talk about different concepts in the Trident animation library for Java applications. Part five talks about supporting Java based UI toolkits, respecting threading rules, custom property interpolators and repaint timelines.
Animations in UI toolkits
Smooth transitions and subdued animations are integral part of many modern graphical applications, and Trident comes with built-in support for Java based UI toolkits. The three UI specific requirements are addressed by the core Trident library:
- Automatically respecting the threading rules of the UI toolkits
- Providing property interpolators for classes that represent graphical objects of the UI toolkits
- Repainting application windows that have continuous animations
Out of the box, Trident supports Swing and SWT. In addition, Trident has a pluggable layer that allows interested applications and third part developers to support additional Java UI toolkits (such as Pivot, Qt Jambi and others).
UI threading rules
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 the special UI thread.
The strictness of the rules is different between the toolkits. For example, Swing allows calling Component.repaint()
off the UI thread – internally the repaint request is scheduled to run on that thread. However, SWT is much stricter – repaints (as well as changing any UI property of widget objects) must be done on the UI thread. Failure to do so results in an SWTException
being thrown by the UI toolkit.
The core Trident library provides a pluggable behavior to automatically detect animations running on UI components and change the interpolated properties on the toolkit UI thread. In addition, custom application callbacks can be marked to be executed on the that thread.
UI property interpolators
Each UI toolkit has its own set of classes that represent visual objects or properties of graphical objects. Classes such as Color
, Point
and Rectangle
are specific to the UI toolkit. In Swing, these are found in the java.awt
package, while in SWT they are located in the org.eclipse.swt.graphics
package.
Applications that wish to interpolate properties of these types require the matching property interpolators. The core Trident library provides built-in property interpolators for both AWT / Swing and SWT graphical classes.
Updating the screen
An update to a property of a graphical object should usually be reflected in the visual representation of that object on the screen. Simple operations – such as changing the foreground color of a UI control – automatically repaint the affected control. However, more complicated application animations affect multiple visual objects many times a second. Such scenarios require periodical update of the screen to reflect all the changes that happened in the application objects since the last repaint.
The core Trident library provides special repaint timelines that are usually run in a repeating loop, repainting the contents of the entire window or specific UI component / container.
Over the course of the next few days i’m going to talk about different concepts in the Trident animation library for Java applications. Part four talks about timeline progress, duration fractions and timeline positions.
Timeline duration
By default, a Trident timeline runs for 500 milliseconds. To change the default timeline duration use the Timeline.setDuration(long)
API, passing the required duration in milliseconds. At runtime, the timeline interpolates all registered properties and notifies all registered listeners. Note that while the number of timeline pulses is directly proportional to the timeline duration, the actual number of pulses, as well as the intervals between each successive pair of pulses depends on the current load of the system and the virtual machine. As such, the application code must not make any assumptions about when the timeline pulses will happen, and how many pulses will happen throughout the duration of the timeline.
The Timeline.setInitialDelay(long)
method specifies the number of milliseconds the timeline should wait after the application code to play()
before starting firing the timeline pulses. For a timeline with no initial delay, the following events are fired:
- idle -> ready immediately after call to
Timeline.play()
- ready -> playing forward immediately afterwards
For a timeline with non-zero delay, the following events are fired:
- idle -> ready immediately after call to
Timeline.play()
- ready -> playing forward after the specified initial delay has passed
Timeline position
Each timeline pulse has two associated fractional values – duration fraction and timeline position. Duration fraction is a number between 0.0
and 1.0
that indicates the absolute percentage of the full timeline duration that has passed. For example, in a timeline that lasts 500 ms, a timeline pulse happening 200 ms after the timeline has begun has the associated duration fraction of 0.4
.
However, some application scenarios require non-linear rate of change for recreating realistic animations of the real physical world. For example, if your application timeline is interpolating the Y
location of a falling ball, strict linear interpolation will result in overly artificial movement. When objects in the real world move, they don’t move at constant speed. For example, a car starts from zero velocity, accelerates to a constant speed, maintains it throughout the main part of the movement and then decelerates to zero velocity as it reaches its final location.
The timeline position is a fractional number between 0.0
and 1.0
that indicates how far the timeline has progressed based on the current ease function. The ease function takes the linearly increasing duration fraction and translates it to the matching timeline position. The Timeline.setEase(TimelineEase)
method allows setting a custom ease function on the timeline, where TimelineEase
interface has the following method:
public float map(float durationFraction)
The org.pushingpixels.trident.ease
package has a number of core ease functions. To illustrate the difference between the different ease functions, we will use the core Spline
ease function. The following screenshot shows the mapping between duration fraction and timeline position under Spline
with factor of 0.4
:

Here, the timeline position has almost linear rate of change throughout the entire duration of the timeline, with little acceleration in the beginning, and little deceleration at the end. Here is the mapping between duration fraction and timeline position under Spline
with factor of 0.8
:

Here, the acceleration phase is longer, and the rate of change between the acceleration and deceleration phases is higher. As you can see, you can simulate different physical processes using different factors of Spline
ease function. Application code can create custom implementation of the TimelineEase
interface as well.
Putting it all together
Interpolation of field values for fields registered for the specific timeline is done based on the timeline position and not duration fraction. Application callbacks registered with the Timeline.addCallback
method get both values in the TimelineCallback.onTimelinePulse
method. This provides the application logic with the information how much time has passed since the timeline has started, as well as how far along the timeline is based on its ease method.
Over the course of the next few days i’m going to talk about different concepts in the Trident animation library for Java applications. Part three talks about APIs to play, loop, resume, suspend and cancel timelines, as well as registering callbacks to listen to timeline life cycle events.
Timeline states
A timeline goes through different timeline states. The Timeline.TimelineState
enum lists all possible timeline states, with the basic ones being:
- Idle for timelines that are not playing. A timeline is idle when it’s been created but not played, or after it has finished playing
- Playing forward for timelines that interpolate fields from start value to end value.
- Playing reverse for timelines that interpolate fields from end value to start value.
- Done for timelines that have finished playing. A done timeline becomes idle after notifying all listeners (see below).
Playing timelines
When the timeline is created, it is in the idle state. An idle timeline can be configured by the application code – even after it has finished playing. The configuration includes adding properties to interpolate, changing the initial delay and duration, adding callbacks and changing the ease function.
To start playing a timeline use the Timeline.play()
method. At every pulse the timeline will interpolate all registered properties (using the public setters), as well as notify all registered callbacks. If the timeline is already playing, it will continue playing from the same point.
Some scenarios required playing the timeline in reverse. In the example below we want to animate the button foreground from blue to red when the mouse moves over the button – this is done in the mouseEntered
method. To provide consistent UI behavior, we also want to animate the foreground color from red back to blue when the mouse is moved away from the button – this is done by calling the Timeline.playReverse()
in the mouseExisted
method.
final Timeline rolloverTimeline = new Timeline(button);
rolloverTimeline.addPropertyToInterpolate("foreground", Color.blue,
Color.red);
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
rolloverTimeline.play();
}
@Override
public void mouseExited(MouseEvent e) {
rolloverTimeline.playReverse();
}
});
Suppose the user moves the mouse over the button and then quickly moves it away. You do not want to have the reverse play start from the end value (full red color) – this will create a noticeable color flicker on the screen. Internally, playReverse()
detects that this timeline is already playing (forward), and starts playing it in reverse from its current position.
Replaying timelines
While play()
and playReverse()
respect the current timeline position for already playing timelines, some scenarios require restarting the timeline. The Timeline.replay()
and Timeline.replayReverse()
can be used in these cases. Code example below shows the replay()
API used to restart the rollover animation that interpolates the background color of a single grid rectangle from yellow to black color:
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();
}
}
Looping timelines
While most timelines need to play only once, some application scenarios require running timelines in loop. Pulsating the system tray icon to indicate new messages or showing an indefinite progress while your application connects over a slow line are examples of looping timelines.
Looping timelines are created and configured in exactly the same way as regular timelines, and they can interpolate float and custom properties of the associated main timeline object. The only difference is the way the looping timeline is played. There are two Timeline
methods to start playing a looping timeline:
-
Timeline.playLoop(RepeatBehavior)
-
Timeline.playLoop(int, RepeatBehavior)
The first method starts an infinite loop (at least until the timeline is canceled). The second method runs the timeline for the specified number of loops. The Timeline.RepeatBehavior
enum specifies what happens when the looping timeline reaches the “end” of the loop. Each timeline loop changes the internal duration fraction which is a number between 0.0
and 1.0
. While a regular timeline ends once the fraction reaches the value 1.0
, a looping timeline continues. The difference between the repeat behaviors is in the way the timeline fraction is computed:
- In the loop mode the timeline fraction starts from
0.0
, is interpolated to 1.0
, and once that value is reached, it is reset it back to 0.0
.
- In the reverse mode, the timeline fraction is interpolated during odd loops from
0.0
to 1.0
, and is interpolated during even loops from 1.0
down to 0.0
.
As an example, the loop mode can be used for circular indefinite progress indication, where the matching “lead” angle is interpolated between 0 and 360 degrees. The reverse mode can be used for displaying indefinite linear progress indication that oscillates between the left and right markers.
Additional timeline operations
A timeline can be put in the suspended state by calling the Timeline.suspend()
method. A suspended timeline can be resumed with the Timeline.resume()
method.
To cancel a playing timeline, call the Timeline.cancel()
method. In addition, there is a method to indicate that a looping timeline should stop once it reaches the end of the loop. For example, suppose that you have a pulsating animation of system tray icon to indicate unread messages. Once the message is read, this animation is canceled in the application code. However, immediate cancellation of the pulsating animation may result in jarring visuals, especially if it is done at the “peak” of the pulsation cycle. Calling Timeline.cancelAtCycleBreak()
method will indicate that the looping animation should stop once it reaches the end of the loop.
Tracking timeline state
Simple application scenarios create timelines, configure them with fields to interpolate and then play them. However, a more complicated application logic may require tracking the state changes of the timeline. The Timeline.addCallback(TimelineCallback)
allows registering a custom callback that will be notified in the following cases:
-
TimelineCallback.onTimelineStateChanged()
– this is called whenever the timeline state is changed. For example, calling Timeline.suspend()
will notify all the registered listeners that the timeline has changed its state from playing to suspended.
-
TimelineCallback.onTimelinePulse()
– this is called on every timeline pulse.
The second method can be used by applications that do not wish to add public setters for all the fields that participate in the timelines. Instead of using the Timeline.addPropertyToInterpolate()
to interpolate the fields via public setters, a timeline callback that interpolates the fields directly in the onTimelinePulse()
can be used.