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));



No comments:

Post a Comment