Trident animation library for Java applications is nearing release 1.1 (code-named Bogeyman), and it’s time to take a look at the new APIs added in this version. This entry is going to talk about supporting Java UI toolkits.
UI toolkit handlers
Graphical applications are a natural fit for animations, and Trident core has built-in support for Swing and SWT. 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 org.pushingpixels.trident.UIToolkitHandler are used to determine the relevance of these threading rules. The UIToolkitHandler interface is illustrated by the core support for SWT:
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);
}
}
This is a very simple implementation of a UI toolkit handler that respects the relevant threading rules:
- The
isHandlerFor associates this handler with all SWT widgets
- The
isInReadyState marks disposed widgets to skip the property interpolation / callback invocations
- The
runOnUIThread 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 TridentConfig 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 org.pushingpixels.trident.UIToolkitHandler interface.
Respecting the threading rules
The UIToolkitHandler.isHandlerFor(Object) 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 Timeline.addPropertyToInterpolate methods will be changed on the UI thread using the UIToolkitHandler.runOnUIThread method.
In the simple Swing example that interpolates the foreground color of a button on mouse rollover, the timeline is configured as
Timeline rolloverTimeline = new Timeline(button);
rolloverTimeline.addPropertyToInterpolate("foreground", Color.blue,
Color.red);
If you put a breakpoint in the JComponent.setForeground(Color) – 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
org.pushingpixels.trident.swing.SwingToolkitHandler registered in the core library returns true for the button object in its isHandlerFor(Object)
- On every timeline pulse, a
Runnable object is created internally. The run() method calls the setters for all registered fields – using the PropertyInterpolator.interpolate method of the matching property interpolator
- This
Runnable is passed to the UIToolkitHandler.runOnUIThread method of the matching UI toolkit handler.
And this is how SwingToolkitHandler.runOnUIThread() is implemented:
@Override
public void runOnUIThread(Runnable runnable) {
if (SwingUtilities.isEventDispatchThread())
runnable.run();
else
SwingUtilities.invokeLater(runnable);
}
Running custom application code on UI thread
The flow described above works for the fields registered with the Timeline.addPropertyToInterpolate methods. What about the custom application callbacks registered with the Timeline.addCallback()? If the callback methods need to respect the UI threading rules of the matching toolkit, the TimelineCallback implementation class needs to be tagged with the org.pushingpixels.trident.callback.RunOnUIThread annotation.
Callback implementations marked with this annotation will have both onTimelineStateChanged and onTimelinePulse invoked on the UI thread, making it safe to query and change the UI. The UIThreadTimelineCallbackAdapter is a core adapter class that is marked with this annotation.
Querying the readiness of the timeline object
The isInReadyState(Object) is the third and final method in the UIToolkitHandler interface. After the specific UI toolkit handler has declared that it will handle the main object of the specific timeline (by returning true from the isHandlerFor(Object) 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 SWTException in the setForeground 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 setForeground should be skipped.
Trident animation library for Java applications is nearing release 1.1 (code-named Bogeyman), and it’s time to take a look at the new APIs added in this version. This entry is going to talk about working with custom property interpolators.
Property interpolators
Trident supports interpolation of primitive values – such as integers, floats and point / color / rectangle classes of supported 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 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 org.pushingpixels.trident.interpolator.PropertyInterpolator interface. This interface has two methods.
The public Class getBasePropertyClass() is used to choose the property interpolator in the Timeline.addPropertyToInterpolate(String, Object, Object). Internally, all registered property interpolators are queried to check whether they support the specified from and to values using the Class.isAssignableFrom(Class). The first property interpolator that has a match for both values will be used.
For example, the PointInterpolator in the core AWT property interpolator source (AWTPropertyInterpolators class) has the following implementation of this method:
public Class getBasePropertyClass() {
return Point.class;
}
The public T interpolate(T from, T to, float timelinePosition) is used to compute the interpolated value during the current timeline pulse. For example, the PointInterpolator in the core AWT property interpolator source (AWTPropertyInterpolators class) has the following implementation of this method:
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);
}
Registering custom property interpolators
TimelinePropertyBuilder.interpolatedWith(PropertyInterpolator) 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 TridentConfig class has the following APIs to work with property interpolators:
-
addPropertyInterpolatorSource(PropertyInterpolatorSource) – registers all the property interpolators provided by this source
-
addPropertyInterpolator(PropertyInterpolator) – registers the property interpolator
-
removePropertyInterpolator(PropertyInterpolator) – unregisters the property interpolator
-
getPropertyInterpolators() – retrieves an unmodifiable list of all registered (core and custom) property interpolators
-
getPropertyInterpolator(Object...) – retrieves the first property interpolator that matches all the passed objects, or null if no match is found
The PropertyInterpolatorSource entries in the 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 org.pushingpixels.trident.interpolator.PropertyInterpolatorSource interface.
This interface has one method – public Set<PropertyInterpolator> getPropertyInterpolators() – which returns a set of custom property interpolators. Custom property interpolators can be used in two ways:
- The
Timeline.addPropertyToInterpolate(String, Object, Object) API that will choose the property interpolator that matches the types of the from and to values
- Use a timeline property builder and the
TimelinePropertyBuilder.interpolatedWith() API. The Timeline.addPropertyToInterpolate(TimelinePropertyBuilder) API will use the specified property interpolator
Bringing it together
Let’s look at the following Swing snippet that has a class with a Point field and a timeline that interpolates the value of that field:
import java.awt.*;
public static class MyRectangle {
private Point corner = new Point(0, 0);
public void setCorner(Point corner) {
this.corner = corner;
}
...
}
Timeline move = new Timeline(rectangle);
move.addPropertyToInterpolate("corner", new Point(0, 0),
new Point(100, 80));
move.playLoop(RepeatBehavior.REVERSE);
What happens when move.addPropertyToInterpolate is called? Internally, the Trident core looks at all available property interpolators and finds that the AWTPropertyInterpolators.PointInterpolator is the best match for the passed values (which are both java.awt.Points). Then, at every pulse of the move timeline, the MyRectangle.setCorner(Point) is called.
Note that the application code did not explicitly specify which property interpolator should be used. The following snippet illustrates the usage of TridentConfig.addPropertyInterpolator API:
private Ellipse2D ellipse;
private static class Ellipse2DPropertyInterpolator implements
PropertyInterpolator {
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 void setEllipse(Ellipse2D ellipse) {
this.ellipse = ellipse;
}
// code to add property interpolator and configure the timeline
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();
Timeline ellipseTimeline = new Timeline(this);
ellipseTimeline.addPropertyToInterpolate("ellipse", from, to);
ellipseTimeline.setEase(new Sine());
ellipseTimeline.setDuration(2000);
ellipseTimeline.playLoop(RepeatBehavior.REVERSE);
Here, the registered property interpolator is implicitly used to animate the expanding / shrinking ellipse.
Trident animation library for Java applications is nearing release 1.1 (code-named Bogeyman), and it’s time to take a look at the new APIs added in this version. This entry is going to talk about setting a custom pulse source to drive the Trident timelines.
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 TridentConfig.setPulseSource(PulseSource) API. The PulseSource interface is:
public interface PulseSource {
public void waitUntilNextPulse();
}
The implementation of waitUntilNextPulse() 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:
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) {
}
}
}
This example uses the blocking Thread.sleep() API. Sample output of running this class is:
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
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 PulseSource implementation to achieve a higher pulse rate.
Trident animation library for Java applications is nearing release 1.1 (code-named Bogeyman), and it’s time to take a look at the new APIs added in this version. This entry is going to talk about extending the core functionality of Trident to address custom needs of the specific applications.
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 float and integer fields of any Java object that provides the matching public setter methods.
- Swing and SWT 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 org.pushingpixels.trident.TridentConfig class contains the published configuration APIs. The TridentConfig.getInstance() API retrieves an instance of this class which can then be used for:
Plugins
A Trident plugin is specified by the META-INF/trident-plugin.properties 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 trident-plugin.properties is simple. Each line in this file should be of the following format:
Key=FullyQualifiedClassName
There supported keys are:
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 META-INF/trident-plugin.properties in the trident.jar
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