June 6th, 2010

Repaint timelines in Trident 1.3

Trident aims to be a general-purpose animation library for Java applications. However, most of the time people talk and refer to animations in the context of pixels on the screen, be it for transitions between different states, rollover effects, smooth scrolling of large content and what not. 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

Today i’m going to talk about the last point – repainting application windows. Trident has two special timelines – org.pushingpixels.trident.swing.SwingRepaintTimeline and org.pushingpixels.trident.swt.SWTRepaintTimeline. These are simple utility timelines that can be used to continuously repaint the contents of the specific window / component on every timeline pulse. For example, if you have a continuous emulation of fireworks, you can have “worker” timelines updating the model objects that represent the firework volleys, and one repaint timeline that updates the entire screen based on the current state of all firework volley model objects. This allows you to separate the model updates from the view updates.

However, as Rob Eden pointed out to me this week, these two classes repaint the window / component on every timeline pulse. This can result in unnecessary repaints if the model updates are not done on every timeline pulse as well. In the “snake” example, the model is updated only when the mouse is moving. Once the mouse has stopped moving and all rollover timelines are done, there are no changes to the model, and continuously repainting the screen consumes unnecessary CPU resources.

To address this, both SwingRepaintTimeline and SWTRepaintTimeline now have three new APIs:

  1. setAutoRepaintMode(boolean) – by default the auto repaint mode is true. Application that wishes to control the repaint will call this method with false.
  2. forceRepaintAtNextPulse() – will make the repaint() / redraw() call when the next pulse is fired. This will throw an exception if the timeline is in auto-repaint mode.
  3. setRepaintRectangle(Rectangle) – allows to dynamically change the rectangle to be repainted. Can be used if the bounds of the view that represents the specific model can change dynamically (e.g. when the user resizes the application window).

The snake examples for both Swing and SWT have been updated to show the recommended usage of the first two APIs. Click the button below to run the WebStart version of the Swing demo:

If you’re interested in testing this functionality in your application, take the latest 1.3dev drop (code named Diamond In The Sky) for a spin.

May 22nd, 2010

Onyx reloaded

Exactly a year ago I’ve introduced project Onyx – animation blueprints for Swing using the Trident animation library. Onyx is a small application that loads album information for the specific artist from the Internet and displays it as a scrollable gallery. Here is how Onyx looked a year ago:

And here is how Onyx looks now:

Along with running on Java 6 (with Java 7 weekly binaries for Mac nowhere in sight), Onyx also has received a slight visual facelift for the main window. First, let’s see the code for disposing a window by fading it out:

public static void fadeOutAndDispose(final Window window,
      int fadeOutDuration) {
   Timeline dispose = new Timeline(window);

   try {
      Class clazz = Class.forName("com.sun.awt.AWTUtilities");
      final Method opacityMethod = clazz.getDeclaredMethod(
            "setWindowOpacity", Window.class, float.class);

      dispose.addPropertyToInterpolate(Timeline. property(
            "opacity").from(1.0f).to(0.0f).setWith(
            new PropertySetter() {
               @Override
               public void set(Object obj, String fieldName,
                     Float value) {
                  try {
                     opacityMethod.invoke(null, obj, value);
                  } catch (Exception exc) {
                     exc.printStackTrace();
                  }
               }
            }));
      dispose.addCallback(new UIThreadTimelineCallbackAdapter() {
         @Override
         public void onTimelineStateChanged(TimelineState oldState,
               TimelineState newState, float durationFraction,
               float timelinePosition) {
            if (newState == TimelineState.DONE) {
               window.dispose();
            }
         }
      });
      dispose.setDuration(fadeOutDuration);
      dispose.play();
   } catch (Exception exc) {
      exc.printStackTrace();
   }
}

With Java 7, you can simply do this:

dispose.addPropertyToInterpolate("opacity", 1.0f, 0.0f);

and let Trident call Window.setOpacity method at every timeline pulse. In Java 6 (starting with 6u10), the AWTUtilities.setWindowOpacity needs to be called on every pulse explicitly.

And here is the code for painting the main content window. First, we store the current clip and enable anti-aliasing:

@Override
protected void paintComponent(Graphics g) {
   Graphics2D g2d = (Graphics2D) g.create();
   Shape clip = g2d.getClip();

   g2d.setStroke(new BasicStroke(1.0f));
   g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
         RenderingHints.VALUE_ANTIALIAS_ON);

Next, we compute the outer and inner contours of the panel (the inner contour will be painted with slightly brighter colors as shown in the screenshot above):

   int radius = 16;

   Shape contour = new RoundRectangle2D.Double(0, 0, getWidth() - 1,
         getHeight() - 1, radius, radius);
   Shape innerContour = new RoundRectangle2D.Double(1, 1, getWidth() - 3,
         getHeight() - 3, radius - 1, radius - 1);

When the main window is first shown and disposed, a special timeline is played to make the window appear and disappear smoothly. The timeline interpolates the alpha field between 0.0 and 0.9. The main panel has two parts – the bright blue header and the black content. While the content is painted based on the current value of alpha, the header uses less translucency:

   g2d.setComposite(AlphaComposite.SrcOver.derive(1.0f - (float) Math.pow(
         1.0f - alpha, 3.0)));

To paint the header, we first update the current clip to make sure that the painting is only done in the header section. Next, we fill the contour, draw the inner contour and draw the outer contour – all based on the specific colors required by the target design:

   // top part
   g2d.clipRect(0, 0, getWidth(), TITLE_HEIGHT);
   g2d.setPaint(new LinearGradientPaint(0, 0, 0, TITLE_HEIGHT,
         new float[] { 0.0f, 0.49999f, 0.5f, 1.0f }, new Color[] {
               new Color(119, 152, 251), new Color(80, 127, 250),
               new Color(48, 109, 250), new Color(10, 97, 250) }));
   g2d.fill(contour);
   g2d.setPaint(new GradientPaint(0, 0, new Color(151, 179, 253), 0,
         TITLE_HEIGHT, new Color(19, 92, 233)));
   g2d.draw(innerContour);
   g2d.setColor(new Color(11, 61, 200));
   g2d.draw(contour);

Now we restore the clip:

   g2d.setClip(clip);

If we have an artist name to display, we position it in the middle of the title area (using font ascent for proper baseline alignment) and paint the string twice to create a lighting effect:

   if (this.searchString != null) {
      g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
      g2d.setFont(UIManager.getFont("Label.font").deriveFont(14.0f)
            .deriveFont(Font.BOLD));
      int fa = g2d.getFontMetrics().getAscent();
      int x = (getWidth() - g2d.getFontMetrics().stringWidth(
            this.searchString)) / 2;
      int y = (TITLE_HEIGHT + fa) / 2;
      g2d.setColor(new Color(31, 60, 114));
      g2d.drawString(this.searchString, x, y + 1);
      g2d.setColor(new Color(255, 255, 255));
      g2d.drawString(this.searchString, x, y);
   }

The main content area is painted in the same way as the header. We update the clip, fill the contour, draw the inner contour and draw the outer contour:

   // bottom part
   g2d.setComposite(AlphaComposite.SrcOver.derive(this.alpha));
   g2d.clipRect(0, TITLE_HEIGHT, getWidth(), getHeight() - TITLE_HEIGHT
         + 1);

   g2d.setColor(new Color(0, 0, 0));
   g2d.fill(contour);
   g2d.setPaint(new GradientPaint(0, TITLE_HEIGHT, new Color(57, 56, 57),
         0, getHeight() - TITLE_HEIGHT, new Color(50, 48, 50)));
   g2d.draw(innerContour);
   g2d.setPaint(new GradientPaint(0, TITLE_HEIGHT, new Color(13, 11, 15),
         0, getHeight() - TITLE_HEIGHT, new Color(15, 8, 13)));
   g2d.draw(contour);

Finally, we draw a single line separator between the title and main content areas:

   // separator
   g2d.setClip(clip);
   g2d.setColor(new Color(12, 11, 12));
   g2d.drawLine(1, TITLE_HEIGHT, getWidth() - 2, TITLE_HEIGHT);

   g2d.dispose();
}

To see the latest Onyx in action, click on the button below to launch the WebStart demo. Use the mouse wheel to scroll between the albums. Click a specific album to show a larger cover and the track list.

If you cannot run the demo, here is a short video of Onyx in action:

April 14th, 2010

Trident 1.2 official release

It is a great pleasure to announce the availability of the final release or version 1.2 of Trident animation library (code-named Cookie Jar). The major milestone for this release is moving Substance 6.0 to use Trident – along with validating the library performance and flexibility to support a wide variety of UI animations. Trident 1.2 has also added a few new APIs to address a few common application requirements:

  • Custom property accessors
  • Refined APIs for stopping timelines

You’re more than welcome to take Trident 1.2 for a ride and see what it can do to add animations to your Swing and SWT applications. To see how Trident is used to drive application animation scenarios, see its use in the following projects:

March 15th, 2010

It’s time for… release candidates

It’s my great pleasure today to announce the availability of release candidates of the following projects:

Substance 6.0 main features include:

  • Support for multi-state animations
  • New look for text-based components
  • Custom component states

Click on the button below to launch a signed WebStart application that shows the available Substance features.

Trident 1.2 is a stabilization release and has a few new APIs.

You’re more than welcome to download the release candidates for Trident, Substance and relevant plugins. If you see any exceptions, visual artifacts or anything else that shouldn’t be there – don’t assume that i know about it. Drop me a line on project forums or mailing lists. The final releases for the libraries are scheduled on April 14.