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

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 the most basic capability of Trident – interpolating properties of Java objects. While the first part is the same as for version 1.0, the subsequent sections describe new functionality in version 1.1.

Interpolating field values

A timeline allows changing field values of the associated object. For example, in a fade-in animation the timeline will interpolate the value of alpha field from 0.0 to 1.0. There are two steps involved in setting up such timeline.

The first step is to create a Timeline instance passing the main object. The timeline is then configured to interpolate one or more fields of this main object. Let’s see a simple example:

public class HelloWorld {
   private float value;

   public void setValue(float newValue) {
      System.out.println(this.value + " -> " + newValue);
      this.value = newValue;
   }

   public static void main(String[] args) {
      HelloWorld helloWorld = new HelloWorld();
      Timeline timeline = new Timeline(helloWorld);
      timeline.addPropertyToInterpolate("value", 0.0f, 1.0f);
      timeline.play();

      try {
         Thread.sleep(3000);
      } catch (Exception exc) {
      }
   }
}

Here, the timeline has the associated HelloWorld instance; this timeline is instructed to interpolate the value field of that instance from 0.0 to 1.0 over the duration of the timeline. There is an important assumption that the application code must honor. Each field added with the addPropertyToInterpolate must have the matching public setter.

A timeline can interpolate multiple fields. In the following example the timeline will change values of three fields at each timeline pulse:

Timeline timeline = new Timeline(circle);
timeline.addPropertyToInterpolate("x", initX, finalX);
timeline.addPropertyToInterpolate("y", initY, finalY);
timeline.addPropertyToInterpolate("opacity", 1.0f, 0.0f);

Configuring interpolation properties

The examples shown above interpolate the specified field from given start value to given end value. Application code that requires finer control over the field interpolation will use the following Timeline APIs:

  • The static Timeline.property(String) method. This returns a TimelinePropertyBuilder object that is used to configure the different aspects of field interpolation – see below.
  • Once the TimelinePropertyBuilder has been fully configured, pass it to the Timeline.addPropertyToInterpolateTo(TimelinePropertyBuilder) API.

Here is a code snippet that illustrates property builders in action:

Timeline pulseCenters = new Timeline();
pulseCenters.addPropertyToInterpolate(
   Timeline. property("opacity").on(this.center1).from(0.0f).to(1.0f));
pulseCenters.addPropertyToInterpolate(
   Timeline. property("opacity").on(this.center2).from(0.0f).to(1.0f));
pulseCenters.addPropertyToInterpolate(
   Timeline. property("opacity").on(this.center3).from(0.0f).to(1.0f));
pulseCenters.setDuration(750);
pulseCenters.setEase(new Spline(0.9f));
pulseCenters.playLoop(RepeatBehavior.REVERSE);

The first line creates a new timeline not associated with any object. The next lines use three property builders to interpolate the opacity field on three separate objects (center1, center2, center3) from 0.0 to 1.0. Finally, we configure the timeline duration and ease, and play it in a reverse loop.

The following APIs are available on the TimelinePropertyBuilder class:

  • from(startValue) and to(endValue) specify the start and end field values for the interpolation.
  • fromCurrent() specifies that the field will be interpolated from its current value.
  • on(object) passes the object whose field will be interpolated. The example above uses this API to interpolate fields of three different objects in one timeline.
  • goingThrough(key frames) specifies the key frames to be used for multi-value interpolation sequence.
  • interpolatedWith(property interpolator) specifies the property interpolator (see below) for field types not supported by the core library.
  • setWith(property setter) specifies the property setter (see below) to be called on every timeline pulse.

Here is another example of using the TimelinePropertyBuilder to interpolate the specific field from its current value to the set end value:

this.scrollTimeline = new Timeline(this);
this.scrollTimeline.addPropertyToInterpolate(
   Timeline. property("leadingPosition").
      fromCurrent().to(this.targetLeadingPosition));
this.scrollTimeline.setDuration(250);

Custom 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. In order to use a custom property interpolator, configure your TimelinePropertyBuilder with the call to interpolatedWith(PropertyInterpolator) API. The org.pushingpixels.trident.interpolator.PropertyInterpolator interface is:

public interface PropertyInterpolator {
   public Class getBasePropertyClass();

   public T interpolate(T from, T to, float timelinePosition);
}

The interpolate method is used at each timeline pulse to compute the interpolated value. Note that if the TimelinePropertyBuilder is not configured with a custom property setter (see below), the application is responsible to make sure that the object containing the interpolated field (either the main timeline object, or the one passed to TimelinePropertyBuilder.on() API) has a public setter accepting the interpolated value returned by the interpolate implementation of this property interpolator.

The PropertyInterpolator.getBasePropertyClass is not used when the application code explicitly sets a property interpolator on the timeline property builder – and it is safe to return any value (including null) from it. This method is used only during dynamic lookup of custom property interpolators.

Custom property setters

The default mechanism to update the interpolated field is to use reflection to look up and call the matching published setter. Application code that does not wish to expose these setters should use the TimelinePropertyBuilder.setWith(PropertySetter) API. The org.pushingpixels.trident.TimelinePropertyBuilder.PropertySetter interface is:

public static interface PropertySetter {
   public void set(Object obj, String fieldName, T value);
}

If the timeline property builder is configured with a custom property setter, this setter will be called at every timeline pulse passing the object, the name of the field and the new value to set on the field. Here is a sample usage of this API:

public class CustomSetter {
   private float value;

   public static void main(String[] args) {
      final CustomSetter helloWorld = new CustomSetter();
      Timeline timeline = new Timeline(helloWorld);
      PropertySetter propertySetter = new PropertySetter() {
         @Override
         public void set(Object obj, String fieldName, Float value) {
            SimpleDateFormat sdf = new SimpleDateFormat("mm:SSS");
            float oldValue = helloWorld.value;
            System.out.println(sdf.format(new Date()) + " : " + oldValue
                  + " - " + value);
            helloWorld.value = value;
         }
      };
      timeline.addPropertyToInterpolate(Timeline. property("value")
            .from(0.0f).to(1.0f).setWith(propertySetter));
      timeline.play();

      try {
         Thread.sleep(3000);
      } catch (Exception exc) {
      }
   }
}

Here, the CustomSetter class does not wish to expose the value field via a public setter. Instead, we use custom property setter to be called on every timeline pulse. Note that while this sample code does update the matching object field, it is not a strict requirement. Your custom property setter can do anything it wants in the set implementation – update a key-value map, update multiple fields or even ignore some of the values altogether.

Deprecated APIs

Version 1.1 deprecates most of the addPropertyToInterpolate and all of the addPropertyToInterpolateTo APIs found in version 1.0. While it is still safe to call them, the deprecated APIs will be removed in the next major release. Application code that is using the deprecated API should be migrated to use the new addPropertyToInterpolate(TimelinePropertyBuilder) API as described in this entry.