Wednesday, July 16, 2008

Pimp My LWUIT Part II: Rounded All Over



In part one of pimp my LWUIT we covered basic gradients and applying them throughout the application, this time we will try to make some of our top level controls rounded.
I talked previously about rounded buttons and the ways to achieve them so I won't dig into that again, I will however talk about making the dialogs/menus rounded since these are not as intuitive to customize especially not in this context.
I would also like to make the dialogs semi transparent allowing an even deeper feel of the applications depth and context.

Note that the current version of LWUIT has a couple of minor border rendering glitches, these will be fixed in the next drop of LWUIT.

Lets begin by making our dialogs more "modern" lets make them look round and semi translucent. This is relatively easy to do as demonstrated by the previous post although there are some minor "gotchas" here. First, is the fact that the title and dialog body are technically separate components. The solution is to use the round border on the dialog body and ignore its title, just never use the title and all will end well (there is a more elaborate trick of creating a "half" round rectangle for the title and the bottom half for the body, but that I will leave as an exercise for you guys).

This is trivial to workaround:
helloForm.addCommand(new Command("Show Dialog") {
public void actionPerformed(ActionEvent ev) {
Dialog.show(null, "Rounded Dialog Body", "OK", null);
}
});
No title...

The second gotcha is the static dialog show methods, they include the body using a TextArea component. Since its opaque and ugly we must theme it to match the dialog.
To solve this we might as well create a default theme of colors to match the common colors, this is much easier than other alternatives for themeing (save maybe for using the resource editor).

To support round dialogs we can just use the getComponentStyle/setComponentStyle pair to set the style we want for the dialog which produces the results you see here.
    public PimpLookAndFeel() {
Hashtable themeProps = new Hashtable();
themeProps.put("fgColor", "dddddd");
themeProps.put("SoftButton.fgColor", "0");
themeProps.put("Title.fgColor", "0");
themeProps.put("fgSelectionColor", "ffffff");
themeProps.put("bgColor", "0");
themeProps.put("bgSelectionColor", "0");
themeProps.put("transparency", "0");
themeProps.put("border", Border.getEmpty());
UIManager.getInstance().setThemeProps(themeProps);

s = UIManager.getInstance().getComponentStyle("Dialog");
s.setBorder(Border.createRoundBorder(10, 10));
s.setBgTransparency(100);
s.setBgColor(0);
s.setFgColor(0xffffff);

UIManager.getInstance().setComponentStyle("Dialog", s);
}
We can also round the menu, using this technique (or even simpler using the resource editor) but that wouldn't be "cool enough"...

Lets extend the linear gradient example from last time into a Border implementation and make it into a rounded gradient border:
/**
* A simple border that can draw a linear gradient as the background of any component.
* The gradient is cached in a mutable image for performance.
*
* @author Shai Almog
*/

public class RoundedBorderLinearGradient extends Border {
private int sourceColor;
private int destColor;
private boolean horizontal;
private Image cache;
private int arcWidth;
private int arcHeight;
private int borderColor;

public RoundedBorderLinearGradient(int sourceColor, int destColor, boolean horizontal, int borderColor, int arcWidth, int arcHeight) {
this.sourceColor = sourceColor;
this.destColor = destColor;
this.horizontal = horizontal;
this.arcHeight = arcHeight;
this.arcWidth = arcWidth;
this.borderColor = borderColor;
}

public boolean isBackgroundPainter() {
return true;
}

public void paintBorderBackground(Graphics g, Component c) {
int x = c.getX();
int y = c.getY();
int height = c.getHeight();
int width = c.getWidth();
if(cache == null || width != cache.getWidth() || height != cache.getHeight()) {
cache = Image.createImage(width, height);
Graphics current = cache.getGraphics();
current.setColor(0);
current.fillRoundRect(0, 0, width, height, arcWidth, arcHeight);
int[] rgb = cache.getRGB();
current.fillLinearGradient(sourceColor, destColor, 0, 0, width, height, horizontal);
int[] rgb2 = cache.getRGB();
for(int iter = 0 ; iter < rgb.length ; iter++) {
if(rgb[iter] == 0xffffffff) {
rgb2[iter] = 0;
}
}
cache = Image.createImage(rgb2, width, height);
}
g.drawImage(cache, x, y);
}

public void paint(Graphics g, Component c) {
int oldColor = g.getColor();
g.setColor(borderColor);
g.drawRoundRect(c.getX(), c.getY(), c.getWidth(), c.getHeight(), arcWidth, arcHeight);
g.setColor(oldColor);
}
}
Now to use this in the code all we have to do is update the PimpLookAndFeel as such:
public class PimpLookAndFeel extends DefaultLookAndFeel {
private Painter formPainer = new RadialGradientPainter(0x999999, 0);
public PimpLookAndFeel() {
Hashtable themeProps = new Hashtable();
themeProps.put("fgColor", "dddddd");
themeProps.put("SoftButton.fgColor", "0");
themeProps.put("Title.fgColor", "0");
themeProps.put("fgSelectionColor", "ffffff");
themeProps.put("bgColor", "0");
themeProps.put("bgSelectionColor", "0");
themeProps.put("transparency", "0");
themeProps.put("border", Border.getEmpty());
UIManager.getInstance().setThemeProps(themeProps);

Style s = UIManager.getInstance().getComponentStyle("Menu");
s.setBorder(new RoundedBorderLinearGradient(0xff0000, 0xffffff, true, 0xff, 10, 10));
s.setBgTransparency(255);
UIManager.getInstance().setComponentStyle("Menu", s);

s = UIManager.getInstance().getComponentStyle("Dialog");
s.setBorder(Border.createRoundBorder(10, 10));
s.setBgTransparency(100);
s.setBgColor(0);
s.setFgColor(0xffffff);
UIManager.getInstance().setComponentStyle("Dialog", s);
}

public void bind(Component c) {
if(c instanceof Form) {
if(!(c instanceof Dialog)) {
c.getStyle().setBgPainter(formPainer);
}
Form f = (Form)c;
f.getTitleStyle().setBgPainter(new LinearGradientPainter(0xffffff, 0xaaaaaa, false));
f.getSoftButtonStyle().setBgPainter(new LinearGradientPainter(0xaaaaaa, 0xffffff, false));
}
}
}

5 comments:

  1. Hi, I need to change the default Dialog size?, Can I do this?.
    Thanks

    ReplyDelete
  2. Hello Shai,

    I followed the code and explanations exactly (right from the beginning of PimpMyLWUIT Part1 until here), I could get the dialog to look exactly as the screenshots (rounded corners, etc.) but I couldn't get the menu to look as in the screenshot no matter what.

    My menu style still follows that of the template(I am using "/javaTheme.res")

    I think the commentor before me also faced the same problem, is there any info that u can give us?

    Thanks for this great framework, spending a lot of time learning how to use it.. but from time-to-time get stuck like this while following your examples..

    ReplyDelete
  3. Lots of changes were made to LWUIT since this post. You can now use the latest LWUIT Designer, rounded borders to achieve the same thing without a single line of code.

    ReplyDelete
  4. Hi shai,

    I am able to use the code and i am able to get the dialog as expected. But my requirement is i have to change only a single dialog and in a single form.If i do this all the dialogs are getting changed.

    tried creating a dialog and setting it a id using setUIID("customdialog"). and while setting i am doing it as UIManager.getInstance().setComponentStyle("customdialog", s);
    But this is not working and i dont know how to set the id for components(other than UIID).
    can you just help me with this??

    ReplyDelete
  5. This is an old post. In the current LWUIT SVN you can just use getDialogStyle() and set it to whatever or better yet you can use setDialogUIID("MyCustomDialog") and then create a custom border style to MyCustomDialog using something like a 9 border.

    ReplyDelete