Skip to main content

Translucent and Shaped Swing Windows

March 18, 2008

{cs.r.title}






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.

History

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, "http://trolltech.com/products/qt">QT, and "http://www.wxwidgets.org/">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 "http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/pantdraw_2him.asp">

SetWindowRgn
documentation on MSDN). The matching functionality
in X11 has been available since 1989 (see the "http://www.xfree86.org/current/shapelib.pdf">X Nonrectangular
Window Shape Extension Library
PDF document). In OS X, you can
just set a non-opaque background color on a
JFrame.

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 the
Robot 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.
"http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6633275">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 or
javax.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.AWTUtilities
class in my "http://www.pushing-pixels.org/?p=260">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 the
AWTUtilities.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 the
AWTUtilities.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 "http://www.pushing-pixels.org/?p=272">Soft clipping and per-pixel
translucency for Swing windows
blog entry. This entry uses
Chris Campbell's "http://weblogs.java.net/blog/campbell/archive/2006/07/java_2d_tricker.html">
tutorial on soft clipping
for the top-left and top-right
corners of the window and Romain Guy's "http://www.jroller.com/gfx/entry/swing_glint">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.

Tooltips

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 "http://www.pushing-pixels.org/?p=95">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 either Robot
or JNI/JNA. Let's see how it can be done with the
AWTUtilities API.

The "http://java.sun.com/javase/6/docs/api/javax/swing/PopupFactory.html">
javax.swing.PopupFactory
is 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 "http://java.sun.com/javase/6/docs/api/javax/swing/PopupFactory.html#setSharedInstance(javax.swing.PopupFactory)">
PopupFactory.setSharedInstance
API 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 Boolean toFade 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
"http://www.curious-creature.org/2007/07/22/repaint-manager-demos-chapter-11/">
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 "http://www.pushing-pixels.org/?p=72">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 is
JReflectionFrame which extends JFrame. The
constructor creates the reflection window and adds a non-double-buffered, non-opaque panel to it. It also overrides the
paintComponent() 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 "http://www.jroller.com/gfx/entry/swing_glint">tutorial on
reflection
.

Conclusion

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 "http://www.curious-creature.org/2007/09/28/translucent-shaped-windows-extreme-gui-makeover/">
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.

Resources


width="1" height="1" border="0" alt=" " />
Kirill Grouchnikov has been writing software since he was in junior high school, and after finishing his BSc in computer science, he happily continues doing it for a living. His main fields of interest are desktop applications, imaging algorithms, and advanced UI technologies.
Related Topics >> Swing   |   

Comments

Hi Guys, any one can reply

Hi Guys, any one can reply me on if we can do some thing like as in following website is possible with java /jna package, Some thing like screen selector in following web site. http://www.screentoaster.com/ click on “start recording”. http://screenr.com/ click on “Record your screen cast now” http://www.screencast-o-matic.com/ click on “Create” button. in java, basically i want to transparent with border and re-sizable.

This is probably a little out

This is probably a little out of date, but I've been playing around with the idea of creating a animated splash screen using transparent windows. My first few attempts failed horribly as the window would not "refresh" between frames, causing the animation to constantly overlayed on top of each other (the animation was been performed within a custom paint method of custom component). I finally stumbled across a solution that seems to have worked for me (on the mac at the mo, need to test it else where)... in the paint method of the window, I called g.clearRect(0, 0, getWidth(), getHeight()); before the super call to the paint method, this seems to have worked a treat. Just thought I'd throw it out there

I realise that it's been a few years since this was written ...

I realise that it's been a few years since this was written - hopefully you're still monitoring the page.

I tried to implement the TranslucentPopup code you have put here and have run the sample code that comes with it. Unfortunately, while it successfully renders the tooltips and popup menus translucent, which is great, it also (for me anyway) renders them inactive. For some reason, I can no longer click on menu items and have them activate. It also occurs in your demo code with your combo box. Am I missing something obvious?

I had the same problem with TranslucentPopup, it appears to ...

I had the same problem with TranslucentPopup, it appears to mishandle any mouse-click on a JMenuItem in a JPopupMenu. Note that activating a JMenuItem via keyboard still appears to work.

I played around with the code a little and discovered that TranslucentPopup#hide() is the culprit, to fix you can just remove the last 2 statements in this method, i.e. do not call popupWindow.removeAll() or popupWindow.dispose().

The explanation for this problem is likely some race-condition between the JMenuItem consuming the mouse event and the hide() method cleaning up.

For more robust implementation of a custom popup, have a look at com.jgoodies.looks.common.ShadowPopup within the JGoodies Looks project.