Sunday, March 14, 2010

Substance L&F: Button with multiline text

Usually when you need a button with text on multiple lines, you have to use html to format the text.

This works fine, but as I am using Substance L&F, I ran into an unsupported featured concerning the foreground colour using html.
First, let’s see how substance displays the text in a button.
The foreground color depend of the button state (default,pressed,armed,…)
There is an animated transition when switching state.

When using html as text the htmlView won’t behave like normal text, I mean by that, that the foreground color will not change because the rendering is delegated to the basicHtml View and not to substance.


SubstanceButtonUI default state with HTML text




SubstanceButtonUI rollover state with HTML text, here you can barely see the text





SubstanceButtonUI default state with normal text





In most of the substance LAF, it’s not big a problem as the foreground color doesn’t change with the button state. But with some LAF as SubstanceMagellanLookAndFeel there is a problem as the text become barely readable during the rollover state.
And even if it stay readable, it just seem strange that some buttons have a black foreground when rollover and some a white one as it’s the case for my custom substance LAF.

So the plan was to make a custom SubstanceButtonUI to manage multiple text lines on JButton, without using the basic html rendering, using \n to split the text.

There are 2 things to do:
To split the text when it’s changing
To paint the text

Splitting the text
Cache of the lines


private String[] splittedText = null;

private class TextChangeListener implements PropertyChangeListener {
        @Override
public void propertyChange(final PropertyChangeEvent evt) {
    if (evt.getNewValue() != null && !evt.getNewValue().toString().isEmpty()) {
        SubstanceMultiLineButtonUI.this.splittedText = evt.getNewValue().toString().split("\n");
    } else {
        SubstanceMultiLineButtonUI.this.splittedText = null;
    }
    SubstanceMultiLineButtonUI.this.button.repaint();
}
};


Install the text change listener
@Override
    protected void installListeners(final AbstractButton b) {
        super.installListeners(b);
        this.textChangeListener = new TextChangeListener();
        b.addPropertyChangeListener("text", this.textChangeListener);
    }


Painting:
Loop through the lines
Find the correct position to paint each line
Paint the line using the paintButtonText from substanceButtonUI
Find the correct position for the icon and paint it.
for (final String s : this.splittedText) {
    viewRect.x = i.left;
    viewRect.y = i.top;
    viewRect.width = b.getWidth() - (i.right + viewRect.x);
    viewRect.height = b.getHeight() - (i.bottom + viewRect.y);

    textRect.x = textRect.y = textRect.width = textRect.height = 0;
    iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;

    String line = SwingUtilities.layoutCompoundLabel(c, fm, s, b.getIcon(), b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, b.getText() == null ? 0 : b.getIconTextGap());

    // find the icon location
    if (b.getIcon() != null) {
        if (trueIconRect.x != 0) {
            trueIconRect.x = Math.min(trueIconRect.x, iconRect.x);
        } else {
            trueIconRect.x = iconRect.x;
        }
        trueIconRect.y = iconRect.y;
        trueIconRect.width = iconRect.width;
        trueIconRect.height = iconRect.height;
    }

    //compute the text Y position
    double part = fontMetrics.getStringBounds(line, g2).getHeight();
    double y = 0.5 * this.button.getHeight();
    y -= part * 0.5 * this.splittedText.length;
    y += part * lineNumber;
    textRect.y = (int) y;
    //deletegate to substance
this.paintButtonText(g2, b, textRect, line);
                        
    lineNumber++;
}





//paint the icon
if (b.getIcon() != null) {
    if (this.splittedText==null||this.splittedText.length==0) {
    // if no text
        SwingUtilities.layoutCompoundLabel(c, fontMetrics, "", b.getIcon(), b.getVerticalAlignment(), b.getHorizontalAlignment(),viewRect, trueIconRect, textRect, b.getText() == null ? 0 : b.getIconTextGap());
    }
    paintIcon(g2, c, trueIconRect);
}


SubstanceMultiLineButtonUI default state




SubstanceMultiLineButtonUI rollover state





To use this UI on a button:
JButton b = new JButton("Do this\nand that");
b.setUI(new SubstanceMultiLineButtonUI(b));



Monday, March 1, 2010

Customs Toggle buttons



First I will talk about why I ended up creating this customs jtoggle button.

I was asked, what would be the best way to show a choice among 2 available options (option A or option B).

Usually when you want the user to select one option among a few (let’s say less than 5) you can use some JRadioButton/JCheckBox/JToggleButton.
JCombobox/JList are usually used if you have more possible options, doesn’t really make sense (at least for me) to have a JComboBox/JList with only 2 or 3 available options

I had to show the icon corresponding to each of the 2 options.
I could have used JRadioButton + a JLabel (to show the icon) or a JToggleButton.
The image being quiet big (> 100*100 pixels), using a JToggleButton is just wrong and plain ugly for this kind of icon.
So my only option left was to use a JRadioButton with a JLabel to show the icon.

After some tests, I wasn’t really satisfied about how it looked.
I really don’t like the look of the selected/unselected icon sticked to the icon representing the option.











So I though why not use directly the image as component with some kind of visual feedback apply to the image (and not outside like for the JRadioButton)

The visual feedbacks I considered for this are the following:
-translucent if not selected
-smaller image if not selected
-a selection ring
And of course it should have animated transition between selected/unselected state switch


You can find the implementation at: ShrinkingToggleImageButton








Some time later, I needed to have a panel to select among 3 options, but this time there was no icon. So I should have just used 3 JRadioButton and be done with it. But considering the application is running on a device with touch-screen and that selecting an option trigger a big change in the screen, it made more sense to have JToggleButton used there.



















As having 3 JToggleButton side by side doesn’t look that great, I decided to see If I could get something that make sense with a better look.


I rapidly decided to try using ShrinkingToggleImageButton, after playing a bit to create the image, I came up with the following result.




















I was happy about the result, it looks way better on the screen it’s used on, than some JRadioButton/JToggleButton.





Some time later, I needed a way to select options among 3 again, but this time it had an icon + some text, as I had the same constraints (touch-screen – big buttons needed), I started to play again with ShrinkingToggleImageButton.

This time I created a class to hold this component, instead of just giving the right image to the shrinkingToggleImageButton: ShrinkingToggleImageAndTextButton

Also it can deal with multiple line text, use \n to wrap to the next line