Translucent and Shaped Swing Windows Blog

Version 2


    Support for translucent and shaped windows has been a long-standing request for the AWT and Swing teams. Even though native applications have had access to this functionality on all major operating systems for quite a few years, it has not been accessible in core Java. This is changing in the upcoming "Consumer JRE," a major update to Java SE 6 which provides an API to create shaped, globally translucent, and per-pixel translucent top-level windows.


    Developers of native applications usually enjoy a greater level of flexibility in developing UI applications. While this comes at the price of restricting an application to a particular platform, in many cases it is outweighed by a richer UI experience and tighter integration with the desktop. Traditionally, cross-platform UI toolkits such as Swing, SWT, QT, and wxWidgets tend to suffer from a well-known dilemma: what to do when only some of the target platforms support a requested feature. In such a case, emulating missing functionality can only get you so far.

    Shaped and translucent windows are perfect examples of the limitations of cross-platform UI toolkits. If a specific target platform does not support this functionality, there is not much you can do, and this can be used as a strong argument against adding this feature to the toolkit. However, the Swing developer community has long since argued that the major target platforms have provided these features for quite some time. In fact, Windows has supported shaped windows since Windows 95 (see the SetWindowRgndocumentation on MSDN). The matching functionality in X11 has been available since 1989 (see the X Nonrectangular Window Shape Extension Library PDF document). In OS X, you can just set a non-opaque background color on aJFrame.

    Up until now, there were three major alternatives available to Swing applications interested in cross-platform translucent and shaped windows:

    The main problem with the first approach is the very use of theRobot class. Even when you have screen capture permission, it must be done before you show your window. In addition, how do you keep the desktop background in sync? Suppose you have a YouTube video playing in the background. Unlike the window-generated events (resize, move), AWT doesn't provide any way to register a listener on repaint of intersected windows. While Chris and Joshua provide a workaround by taking a snapshot at least every second, it is not enough for overlaying background video playback. Furthermore, your window needs to be hidden before every snapshot; this can result in visible flickers.

    Using JNI and JNA results in significant visual fidelity improvements. Pure JNI comes with a steep price: you need to bind to the relevant APIs of each one of the target platforms and bundle the native libraries. JNA does the heavy lifting for you; it bundles the native libraries and provides a class loader that is capable of extracting and loading them at runtime. It supports Linux, OS X, Windows, Solaris, and FreeBSD.

    Consumer JRE

    Java SE 6 Update N, commonly known as Consumer JRE, is an effort by Sun to reposition Java as a viable alternative for developing rich desktop applications. The list of new features and major improvements in Consumer JRE is quite extensive, and a particularly shiny gem is hidden inside the release notes of one of its latest weekly builds. Bug 6633275 is titled simply "Need to support shaped/translucent windows." However, the possibilities that this new core JDK feature brings to Swing developers are quite far-reaching. The remainder of this article will show just a few examples of what can be done, and how.

    Before continuing any further, there is a very important note. Since Consumer JRE is officially considered a minor update to a stable JDK release, it cannot add any new APIs (classes, methods, etc.) in the "public" packages, such as java.awt orjavax.swing. All the APIs discussed in this article appear in the new com.sun.awt.AWTUtilities class, which is not a part of officially supported API. Most probably its location will change in Java SE 7, and the methods signatures might change slightly between now and the final Consumer JRE release. So be ready to change your own code when that happens.

    The AWTUtilities Class

    I first talked about the com.sun.awt.AWTUtilitiesclass in my Translucent and shaped windows in core Java blog entry. Let's start from a simple window in Figure 1:

    A window with few controls
    Figure 1. A window with few controls

    To make it translucent, you can use theAWTUtilities.setWindowOpacity(Window, float) method, as illustrated in Figure 2:

    Translucent window
    Figure 2. Same window, but with 50 percent opacity

    To make it shaped, you can use theAWTUtilities.setWindowShape(Window, Shape) method as illustrated in Figure 3:

    Shaped window
    Figure 3. Same window, but clipped by an oval

    As you can see from Figure 3, a shaped window does not look very good. Its edges are aliased, and the overall impression is not very clean. To achieve better visuals for shaped windows you need to use the AWTUtilities.setWindowOpaque(Window, boolean) API and paint the background of the window with soft clipping. This is illustrated in the follow-up Soft clipping and per-pixel translucency for Swing windows blog entry. This entry uses Chris Campbell's tutorial on soft clipping for the top-left and top-right corners of the window and Romain Guy's tutorial on reflection, including the improvement by Sebastien Petrucci. Figure 4 shows a soft-clipped window with per-pixel translucency:

    A window with soft clipping and per-pixel translucency
    Figure 4. A window with soft clipping and per-pixel translucency

    Now that we have these APIs at our fingertips, what are we going to do? The possibilities are quite intriguing, and to explore them we're going to take a look at a few assorted examples.


    How about making application tooltips translucent? This is quite easy to achieve today for lightweight tooltips since they are painted as part of the Swing top-level window. (See the Glass panes and lightweight pop-up menus entry for more information on lightweight pop-ups.) However, once the tooltip becomes heavyweight and "breaks" the window bounds, you need to fall back on eitherRobot or JNI/JNA. Let's see how it can be done with the AWTUtilities API.

    The javax.swing.PopupFactoryis the factory for creating pop-ups. A tooltip is just one example of pop-up functionality; other examples include combo-box drop-down lists and menus. The PopupFactory.setSharedInstanceAPI can be used to set a custom pop-up factory, and this is what we will do. The current pop-up factory is used to create all application pop-ups, and we will install a custom opacity factor on all tooltips.

    The core pop-up factory implementation is quite complex. It first tries to create a lightweight pop-up, and when a heavyweight pop-up is required, it manages a cache to reuse previously created pop-up windows. Our implementation will always create a new heavyweight pop-up; running different scenarios on a relatively recent laptop has not revealed any noticeable performance hit. Let's start with a custom pop-up factory:

    public class TranslucentPopupFactory extends PopupFactory { @Override public Popup getPopup(Component owner, Component contents, int x, int y) throws IllegalArgumentException { // A more complete implementation would cache and reuse // popups return new TranslucentPopup(owner, contents, x, y); } } 

    The implementation of TranslucentPopup is quite simple. The constructor creates a new JWindow, sets its opacity to 0.8 for tooltips, and installs a custom border from the Looks project that provides a drop-shadow effect:

    TranslucentPopup(Component owner, Component contents, int ownerX, int ownerY) { // create a new heavyweight window this.popupWindow = new JWindow(); // mark the popup with partial opacity com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow, (contents instanceof JToolTip) ? 0.8f : 0.95f); // determine the popup location popupWindow.setLocation(ownerX, ownerY); // add the contents to the popup popupWindow.getContentPane().add(contents, BorderLayout.CENTER); contents.invalidate(); JComponent parent = (JComponent) contents.getParent(); // set the shadow border parent.setBorder(new ShadowPopupBorder()); } 

    Now we need to override Popup's show()method to mark the entire pop-up window as non-opaque. This is needed for the drop shadow border that has per-pixel translucency.

    @Override public void show() { this.popupWindow.setVisible(true); this.popupWindow.pack(); // mark the window as non-opaque, so that the // shadow border pixels take on the per-pixel // translucency com.sun.awt.AWTUtilities.setWindowOpaque(this.popupWindow, false); } 

    The hide() method just hides and disposes the pop-up window:

    @Override public void hide() { this.popupWindow.setVisible(false); this.popupWindow.removeAll(); this.popupWindow.dispose(); } 

    To install this pop-up factory, simply call

    PopupFactory.setSharedInstance(new TranslucentPopupFactory()); 

    Figure 5 shows a sample frame with the translucent tooltip. Note the consistency of the visuals (translucency and drop shadow border) as the tooltip crosses the Swing frame bounds and extends into the background Eclipse window:

    Translucent tooltip
    Figure 5. Translucent tooltip

    Now let's do some animation. How about fading in the tooltip when it's shown and fading it out when it's hidden? Once you're familiar with the AWTUtilities APIs, it's not difficult to do. Here is the code for the show()method:

    @Override public void show() { if (this.toFade) { // mark the popup with 0% opacity this.currOpacity = 0; com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow, 0.0f); } this.popupWindow.setVisible(true); this.popupWindow.pack(); // mark the window as non-opaque, so that the // shadow border pixels take on the per-pixel // translucency com.sun.awt.AWTUtilities.setWindowOpaque(this.popupWindow, false); if (this.toFade) { // start fading in this.fadeInTimer = new Timer(50, new ActionListener() { public void actionPerformed(ActionEvent e) { currOpacity += 20; if (currOpacity <= 100) { com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow, currOpacity / 100.0f); // workaround bug 6670649 - should call // popupWindow.repaint() but that will not repaint the // panel popupWindow.getContentPane().repaint(); } else { currOpacity = 100; fadeInTimer.stop(); } } }); this.fadeInTimer.setRepeats(true); this.fadeInTimer.start(); } } 

    Here, we mark the pop-up window with zero percent opacity. Then we start a repeating timer for five iterations. At every iteration, we increase the window opacity by 20 percent and repaint it. Finally, we stop the timer. The end visual result is a smooth fade-in sequence of the tooltip appearance; this sequence lasts for about 250 milliseconds.

    The hide() method is very similar:

    @Override public void hide() { if (this.toFade) { // cancel fade-in if it's running. if (this.fadeInTimer.isRunning()) this.fadeInTimer.stop(); // start fading out this.fadeOutTimer = new Timer(50, new ActionListener() { public void actionPerformed(ActionEvent e) { currOpacity -= 10; if (currOpacity >= 0) { com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow, currOpacity / 100.0f); // workaround bug 6670649 - should call // popupWindow.repaint() but that will not repaint the // panel popupWindow.getContentPane().repaint(); } else { fadeOutTimer.stop(); popupWindow.setVisible(false); popupWindow.removeAll(); popupWindow.dispose(); currOpacity = 0; } } }); this.fadeOutTimer.setRepeats(true); this.fadeOutTimer.start(); } else { popupWindow.setVisible(false); popupWindow.removeAll(); popupWindow.dispose(); } } 

    First it checks whether the fade-in sequence is still running, and cancels it as needed. Then, instead of immediately hiding the window, it changes its opacity from 100 percent to zero percent in increments of 10 (so that the fade-out sequence is twice as slow as the fade-in), and only then hides and disposes of the pop-up window. Note that both methods consult the BooleantoFade variable -- it is set to true on tooltips only. Other types of pop-ups (menus, combo box drop-downs) do not have fade animations.

    Video Reflection

    Now let's do something a little bit more exciting. In his Repaint Manager Demos (Chapter 11) blog entry, Romain Guy showed a Swing component that provides reflection capabilities. Taken from the book Filthy Rich Clients, which he co-authored with Chet Haase, the test application shows this component providing realtime reflection of a QuickTime movie. How about taking the reflection outside the window bounds?

    First, here is a screenshot of our reflection frame in action. Figure 6 shows a regular Swing frame that plays one of "Get a Mac" ads (using the embedded QuickTime player), along with a translucent realtime reflection that overlays the desktop:

    Reflection of a QuickTime movie
    Figure 6. Reflection of a QuickTime movie

    This implementation reuses a few building blocks from Romain and extends them to the "out of frame" world. It has a custom repaint manager (see the Validation overlays using repaint manager entry for more information on repaint managers) to keep the reflection window in sync with the main frame contents. It also registers a component listener and window listener on the main frame to make sure that the reflection window is kept in sync with the visibility, location, and size of the main window. In addition, it has a custom root pane that paints its contents to an offscreen buffer. This offscreen buffer is then used to paint both the main frame and its reflection in the reflection window.

    Let's see some code. The main class isJReflectionFrame which extends JFrame. The constructor creates the reflection window and adds a non-double-buffered, non-opaque panel to it. It also overrides thepaintComponent() of that panel to paint the reflection of the main frame contents. After initializing the location and size of the reflection frame, we install a custom repaint manager.

    public JReflectionFrame(String title) { super(title); reflection = new JWindow(); reflectionPanel = new JPanel() { @Override protected void paintComponent(Graphics g) { // paint the reflection of the main window paintReflection(g); } }; // mark the panel as non-double buffered and non-opaque // to make it translucent. reflectionPanel.setDoubleBuffered(false); reflectionPanel.setOpaque(false); reflection.setLayout(new BorderLayout()); reflection.add(reflectionPanel, BorderLayout.CENTER); // register listeners - see below ... // initialize the reflection size and location reflection.setSize(getSize()); reflection.setLocation(getX(), getY() + getHeight()); reflection.setVisible(true); // install custom repaint manager to force re-painting // the reflection when something in the main window is // repainted RepaintManager.setCurrentManager(new ReflectionRepaintManager()); } 

    Here are the listeners that keep the reflection window in sync with the main frame:

    this.addComponentListener(new ComponentAdapter() { @Override public void componentHidden(ComponentEvent e) { reflection.setVisible(false); } @Override public void componentMoved(ComponentEvent e) { // update the reflection location reflection.setLocation(getX(), getY() + getHeight()); } @Override public void componentResized(ComponentEvent e) { // update the reflection size and location reflection.setSize(getWidth(), getHeight()); reflection.setLocation(getX(), getY() + getHeight()); } @Override public void componentShown(ComponentEvent e) { reflection.setVisible(true); // if the reflection window is opaque, mark // it as per-pixel translucent if (com.sun.awt.AWTUtilities.isWindowOpaque(reflection)) { com.sun.awt.AWTUtilities.setWindowOpaque(reflection, false); } } }); this.addWindowListener(new WindowAdapter() { @Override public void windowActivated(WindowEvent e) { // force showing the reflection window reflection.setAlwaysOnTop(true); reflection.setAlwaysOnTop(false); } }); 

    The repaint manager is quite simple: it enforces the repaint of the entire root pane of the main frame and then updates the reflection window. This can be optimized to only sync the reflection of the updated region; for the purposes of our sample application (full-frame video) it is enough:

    private class ReflectionRepaintManager extends RepaintManager { @Override public void addDirtyRegion(JComponent c, int x, int y, int w, int h) { Window win = SwingUtilities.getWindowAncestor(c); if (win instanceof JReflectionFrame) { // mark the entire root pane to be repainted JRootPane rp = ((JReflectionFrame) win).getRootPane(); super.addDirtyRegion(rp, 0, 0, rp.getWidth(), rp.getHeight()); // workaround bug 6670649 - should call reflection.repaint() // but that will not repaint the panel reflectionPanel.repaint(); } else { super.addDirtyRegion(c, x, y, w, h); } } } 

    The painting code of the main frame (offscreen buffer) and reflection window was described at length in Romain's tutorial on reflection.


    It has been long awaited, and now it's finally here. Even though the APIs for creating translucent and shaped windows are not in the officially supported packages, they can still be used for creating visually rich cross-platform UIs. The Translucent-Shaped Windows (Extreme GUI Makeover) entry from Romain's blog showcases the JNA project to create a visually compelling use of an animated translucent shaped window. Now you can do the same with the core JDK. This article walked through three sample examples that show the new core JDK APIs in action. I'm sure that you can think of more.


    • Sample code for this article
    • Overview of the Swing painting pipeline
    • Chapteron using the Robot class to emulate transparent windows, from Swing Hacks by Joshua Marinacci and Chris Adamson
    • JNA project by Timothy Wall
    • Introductionto the basic APIs for creating translucent and shaped windows in Consumer JRE
    • Introductionto the per-pixel translucency and soft-clipped shaped windows in Consumer JRE
    • Tutorial on creating translucent reflection by Romain Guy
    • Tutorialon soft clipping by Chris Campbell
    • Overviewof video reflection component from the book Filthy Rich Clients by Chet Haase and Romain Guy