Thursday, April 05, 2007

Animated Per-panel Options Pane

Here's another example in the search for an unobtrusive property editor. This one looks a little like the Google Maps thumbnail navigator (at least with respect to its activator).


The API is simple:

// This component's preferred size will normally be that of its content
container.add(new OptionsPanel(content, options));

The purpose of this component is to unobtrusively associate a nontrivial set of options with some content whose display is typically larger than the options themselves. The options display is kept close the the content it modifies, which avoids the user having to go search through menus or play dialog positioning games. Since the options are hidden by default, you also don't have them always using up screen real estate. This sort of thing is often wedged into a docking solution, which is inappropriate if the information doesn't need to be visible at all times. A docking solution also insists on allocating the whole edge of the dock, so if you don't have other junk that has to be in there, you're wasting a lot of space.

The OptionsPane component wraps the content in the center of a BorderLayout and keeps the PAGE_END slot available for the options themselves.

Toggle Button Positioning

Notice that the toggle button isn't affected by the border layout (or any other content, for that matter). The toggle button has been placed in a layer above the rest of the content, so it's always visible and doesn't contribute to any layout complexity.

JRootPane root = SwingUtilities.getRootPane(this);
if (root != null) {
layered = root.getLayeredPane();
if (layered != null) {
int layer = JLayeredPane.DEFAULT_LAYER.intValue();
layered.add(toggle, new Integer(layer + 1));

We also want to update the button location whenever the OptionsPane moves. While we could use a ComponentListener, that would update the button's location after the OptionsPane was moved/resized, resulting in some jerkiness to the display. So instead, the button gets moved at the same time as the parent component:

public void setBounds(int x, int y, int w, int h) {
super.setBounds(x, y, w, h);

Layout Animation

Rather than instantly making the options visible and doing a single relayout, we use a javax.swing.Timer to gradually grow the options from the lower right corner into its final position. The bottom slot of BorderLayout will respect a component's height, but stretch or squash the component to the pane's current width. So changing the height gradually will work, but changing the width will have no effect (the component simply appears to rise from the bottom of the panel, rather than from the lower right).

Rather than changing the width, we use an empty border in a JPanel wrapped around the options component. By changing the width of the left border, the options appear to grow from the left to the right.

The animation increment is simple (move half the remaining distance), which is pretty good for most purposes. This is the Timer configuration in response to the expansion button click:

final int INTERVAL = 50;
final int FRACTION = 2;
final boolean expanded = isExpanded;
Timer timer = new Timer(INTERVAL, new ActionListener() {
public void actionPerformed(ActionEvent e) {
Insets insets = optionsBox.getInsets();
Insets delta = targetInsets(expanded);
Dimension targetSize = targetSize(expanded);
int dx = (delta.left - insets.left)/FRACTION;
if (dx != 0) {
insets.left += dx;
optionsBox.setBorder(new EmptyBorder(insets));
int dy = (targetSize.height - optionsBox.getHeight())/FRACTION;
if (dy != 0) {
Dimension size = optionsBox.getSize();
size.height += dy;
if (Math.abs(dx) <= 1 && Math.abs(dy) <= 1) {
if (!expanded) {

You can play around with the FRACTION and INTERVAL constants to see the effect it has on the animation.

Source is available in the applet jar file in the article link.

No comments: