The Source for Java Technology Collaboration
User: Password:



Romain Guy

Romain Guy's Blog

Make Swing... er... Swing!

Posted by gfx on February 07, 2006 at 01:24 AM | Comments (21)

Most GUI are really boring. And I really mean it. Admit it, you'd rather listen to a French stammerer trying to recite a bad English translation of War and Peace during a rainy Sunday afternoon (and boy what a long afternoon it would be) than look yet again at some applications. Besides cool aesthetics, a way to make a GUI for appealing (just talking about the look here) is to introduce animations. Some get it right (Apple), and some get it really wrong (the first thing I do with a fresh Windows install - after muting the sounds - is to shut down all the animations). But that's not the point. Bad or good, we just want animations in our Swing application.

javax.swing.Timer is the preferred way to animate a component, an effect or whatever in Swing. Despite a lack of precision, no better than the native timing resolution (which would be around 15ms on Windows), this timer is really easy to use as it relies on ActionListener and as it posts all its events in the EDT. Unfortunately, this timer's API is very crude and gives you the bare minimum:

Timer crudeTimer = new Timer(1000 / 30, new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    }
});
crudeTimer.start();

That, is a simple timer firing an event 30 times per second (or so it claims at least.) So, what is missing here? Too many things to make animations easy to develop. First, this timer doesn't track the elapsed time. You have to do that by yourself using System.currentTimeMillis() or better yet System.nanoTime(). This timer also doesn't care about the duration of the animation. You have to test the elapsed time against a given delay and stop the timer accordingly, using this beautiful expression: ((Timer) e).stop(). What about repeating an animation? Going back and forth? You get the point, Timer's useful but way too generic for our purpose. I have written so many Timer/ActionListener pairs full of tangled and ugly Java code that I feel pretty confident to herein conclude about the timers: @#!.

Let me introduce you to the solution of the wise man, Chet Haase's timing framework. Before you run away bewildered by such a powerful imagination to name a framework, take a deep breath, sit down and stay focused for a couple more minutes, will you? It's worth it. I swear. Chet ran into the exact same problems as the aforementioned ones. But Chet is not lazy, Chet is full of resources. Chet built a solution to this problem. Bestow our eternal gratitude on Chet (or whatever that is you want to bestow on Chet.) Anyway, Chet wrote a comprehensive and really interesting introduction to his framework that I urge you to read. I won't delve into the details here but the point is, hitherto you were striving to create animations for Swing UI and now it's much easier. How so? Let's take a look at a simple example a nice looking but boring, static button:



Evidence #1. A boring button.


Usually, Swing developers make buttons more interactive by introducing a rollover effect. The nice thing is you just need to call somethin like JButton.setRolloverIcon() to make it work. Unfortunately, the change is sudden and doesn't look so sexy. The solution is to animate the rollover effect. In my case, I added a highlight in the background that gradually appears when the mouse moves over the button. The animation is vey fast (about 300 ms) so it's not annoying but you can still notice it. Here is the animated result:



Evidence #2. A cool button (and animated)


When the mouse exits the button, the contrary happens, and the highlights fades out. In this particular case, I took care of animation consistency. For instance, if you enter the button then exits halfway throught the animation then enter again, you will see the animation react properly. It won't jump to its extreme values. Believe me, that's a pain to handle with javax.swing.Timer. Now, thanks to Chet's timing framework, things are way better than they used to be:

private final class HiglightHandler extends MouseAdapter {
    private TimingController timer;
    private Cycle cycle = new Cycle(300, 1000 / 30);
    private Envelope envelope = new Envelope(1, 0,
                                             RepeatBehavior.FORWARD,
                                             EndBehavior.HOLD);
    
    @Override
    public void mouseEntered(MouseEvent e) {
        animate(true);
    }
    
    @Override
    public void mouseExited(MouseEvent e) {
        animate(false);
    }

    private void animate(boolean forward) {
        if (timer != null && timer.isRunning()) {
            timer.stop();
        }
        timer = new TimingController(cycle, envelope, new AnimateGhost(true));
        timer.start();
    }
}

private final class AnimateGhost implements TimingTarget {
    private boolean forward;
    private float oldValue;

    AnimateGhost(boolean forward) {
        this.forward = forward;
        oldValue = ghostValue;
    }

    public void timingEvent(long cycleElapsedTime,
                            long totalElapsedTime,
                            float fraction) {
        ghostValue = oldValue + fraction * (forward ? 1.0f : -1.0f);
        repaint();
    }

    public void begin() {
    }

    public void end() {
    }
}

This code might seem a bit long but when you look more closely you'll see it is very easy. You can see a mouse adapter that handle the mouse enter/exit events and start the animation accordingly. All three fields from HiglightHandler uses classes from Chet's timing framework. The Cycle defines the total duration of the animation as well as its number of frames per second. The Envelope describes the animation itself: it is played once, with no initial delay, it goes forward in time (to get a time fraction going from 0.0 to 1.0) and holds its last value when the animation is over. Finally, the TimingTarget is where the nicest thing happens: the timingEvent() callback gives you everything you need to animate your GUI. In our case we just use the fraction (our position in time) and use it to modify the ghostValue. This value is just used as the transparency level of the white highlight you can see in the second screenshot.

As you can see, this small framework makes things easier both for the author of the code but also for the readers. Chet, Chris and I are currently using this framework almost everyday and as we go along, Chet is discovering new things to integrate into it. You can expect really nice things in a near future, like non-linear progressions. In the meantime, give it a try, I'm sure you will love it.

I couldn't post a webstart demo of the small effect I presented here but I'll try to do that this week if I have some time. Yet, you can download the animation (QuickTime format, 46kb) to see what it looks like. You can also check the talk Desktop Java in Action Richard Bair and I gave at JavaPolis. At the end of the presentation, in the FX section, I talk more about how to use Swing's timers for animations and the related problems.


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Romain,
    The next release of Substance LAF (RC next week, release in three weeks) adds fade-in / fade-out animations on all buttons (including toggle, radio and check boxes, combo box buttons and scroll buttons), tabs and scroll thumbs. You can try the WebStart version to see it in action. Substance uses its own implementation for efficient animation - it's a single thread that tracks all currently animated components. Try moving the mouse quickly over a group of buttons back and forth and notice the CPU usage.

    Posted by: kirillcool on February 07, 2006 at 02:53 AM

  • I'm glad people are working on modern swing styles, and the screen shots look nice, but back to why applets didn't take off, I get this message when I try to run the WebStart at this particular link. I'm sure there's some setting or special vm I must download (although I've let it update itselft to the latest and checked my proxy config), but it just plain doesn't work.

    JNLPException[category: Download Error : Exception: java.net.SocketException: socket closed : LaunchDesc: null ]
    at com.sun.javaws.cache.DownloadProtocol.doDownload(Unknown Source)
    at com.sun.javaws.cache.DownloadProtocol.isLaunchFileUpdateAvailable(Unknown Source)

    Posted by: tcowan on February 07, 2006 at 11:05 AM

  • You get shiny animated buttons or useful error messages; not both.

    Also, a lot of "cool" UIs usually end up sucking (see everything Creative have ever produced). I don't think that "boringness" is a very good metric for usability.

    Posted by: davetron5000 on February 07, 2006 at 12:11 PM

  • Let me quote myself: a way to make a GUI for appealing (just talking about the look here.) I am NOT trying to deal with usability here. And nothing prevent you from doing both.

    Posted by: gfx on February 07, 2006 at 12:32 PM

  • By the way, shouldn't it be new AnimateGhost(forward) instead of new AnimateGhost(true)? Speaking of Chet's framework - does it allow coalescing the targets, or does it use the time-difference queue with a single thread dispatcher?

    Posted by: kirillcool on February 07, 2006 at 12:50 PM

  • Indeed, a stupid mistake I did while cleaning the code for this blog entry :) Thanks I'm gonna fix this. Concerning Chet's framework it doesn't allow such things yet but it's sitll in heavy work in progress.

    Posted by: gfx on February 07, 2006 at 12:57 PM

  • I love the effect.

    Posted by: imjames on February 07, 2006 at 12:59 PM

  • Liked the demo and noticed that the webstart demo applications main window frame is skinned, can synth skin the application main window frame?

    Posted by: herpgps on February 07, 2006 at 03:06 PM

  • Just wondering how you actually created the custom button image. It's a really slick looking button. Are you overriding a buttons paint method or something similar to that?

    Posted by: powerdroid on February 07, 2006 at 06:02 PM

  • I really love this button. It might seem strange to get so excited over a simple button, but its so hard to find a good component design. I think the nicest looking buttons are actually the XP buttons. But have you seen the buttons in Windows Vista? http://weblogs.java.net/blog/chet/images/SwingSetb.PNG I think they're HIDEOUS. But Romain's darker design is really nice (it resembles the Vista taskbar, which is sweet looking). I'd LOVE to see a whole look and feel with this style, and all animated.
    I think Kirill has done an incredible job with his Substance L&F. The menus even have drop shadows! (if he can do it, why cant the windows look and feel?) The comment he made here about a single thread tracking animated components for efficient animation is really interesting. If you're reading this, perhaps you could do a blog entry going in to some detail about how this works.

    Hopefully in a future release of Swing we'll some of this work, along with Chets and Romains, integrated to give Swing some native animation support, also important for things like animated menus and combobox drop downs.

    So although I think Kirills work is a great technical achievement, I'm afraid I just dont like the look of it. I feel bad for saying this because he's done such a great job. The main thing I dont like I think is the overly bright colors. It seems to work ok for OSX, but in Substance it all seems a bit "much". Although the buttons look beautiful at huge sizes in the vertical buttons demo, and the progress bars look sweet! I dont like the round buttons, Romain's have a much nicer shape. And look how licely it blends in with the background. Same with the XP buttons. On Substance I think they just stand out a bit too much.

    On the other hand, I guess its just a matter of what you're used to...

    Posted by: benloud on February 07, 2006 at 07:10 PM

  • On another note... this post really shows the power of Swing. For Vista, Microsoft had to do away with Windows Forms and create an entirely new UI framework for its fancy effects. Swing has been around since... when? 1997? And yet its still able to be extended to compete with modern UIs without starting a new framework again from scratch. And even accomodating 3D with the work that Chris Cambell has been doing : http://weblogs.java.net/blog/campbell/archive/2005/09/index.html

    This is all exciting stuff!

    Posted by: benloud on February 07, 2006 at 07:21 PM

  • powerdroid: It's a PNG file stretched over the button surface in a subclass of JButton. The text is also painted by the subclass to add a drop shadow and antialiasing.
    benloud: If you like the button, you should see the complete UI then, you would love it ;-) If you want a smilar look, try the Synthetica theme called Black Star. It's really slick, I love it.

    Posted by: gfx on February 07, 2006 at 07:24 PM

  • @benloud: Speaking of 3D with Swing, take a look at Twinkle and 3D Button if you haven't already :)

    Posted by: gfx on February 07, 2006 at 07:33 PM

  • that substance demo is gorgeous. excellent work, really.

    Posted by: ilazarte on February 07, 2006 at 08:37 PM

  • benloud,
    Thanks for the comments on Substance. About the fluorescent colors - Substance comes with 20+ core themes and 20+ more in the theme pack. Some of them are very (very very) bright, some of them are cool / earth (olive, sepia, blue steel), some are dark. I'm sure you find one (or more) to your liking, and if not, you'll get much more in the next version. In addition, you can change the button shaper to Classic (2-pixel round corners). You can test all this in the demo menus (themes / button shapers).
    One other thing - i don't want to take credit for something that was done by others. The drop shadows on menus and tooltips were taken from JGoodies' Looks library.

    Posted by: kirillcool on February 07, 2006 at 11:59 PM

  • For animation purposes, it seems reflection could really offer a helping hand. I would love to do something like this:

    animator.animate(component, "background", startColor, endColor, animationOptions...);

    Where animationOptions would define things like duration, resolution, repeat behavior, etc. More advanced things could be defined as well:

    animator.beginGroup(...);
    animator.animate(component, "background", startColor, endColor, options...);
    animator.animate(component, "bounds", startBounds, endBounds, options...);
    animator.animateGroup(...);

    I seem to recall something like this existing somewhere out there?

    Posted by: adepue on February 08, 2006 at 08:51 AM

  • Substance is by far the best LnF in my opinion.
    For some time now i try to avoid some 'tips' there, as every tip needs a thread or swingworker. If you use too much of them isn't good.
    Take a look at substance all animation/blending is done with one thread, thats good design.br

    regards,
    Jens

    Posted by: mac_systems on February 08, 2006 at 12:33 PM

  • @adepue: Watch out timingframework's CVS, Chet is working on that right now :))
    @Jens: I strongly disagree with you but that's a matter of taste. As for the tips, most animated effects are very unlikely to run at the same time. That said, the one thread approache is nice in Substance's case.

    Posted by: gfx on February 08, 2006 at 01:22 PM

  • @GFX: Well, it's right that you need to run several threads you recive desired result. But having this threads in a real world appication can result in some trouble. Looking in a debugger or profiler also needs human readable names for each else you get lost.

    Posted by: mac_systems on February 08, 2006 at 04:34 PM

  • Although not directly related to your code, all the comments regarding good LAFs has got me wondering. Is it possible to have an application which has several gui classes (maybe each extending JPanel with its own collection of components, etc) each using a different LAF? Or, can each app only have one LAF?

    Posted by: powerdroid on February 08, 2006 at 06:50 PM

  • @powerdroid : No, its possible to give each window or only selected components an individial look and feel.

    Posted by: mac_systems on February 08, 2006 at 06:58 PM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds