Monday, November 16, 2009

Rounded Fading Galore

The look of some modern phones is wasteful in screen real-estate but stunning in visual effect. The rounded UI and faded scrolling are are gorgeous effects on newer devices that make a huge difference with very little work.

These effect can be added to LWUIT easily without overhauling anything. I neglected to add padding to the softbutton area in the demo code but other than that the code is trivial. I used a glass pane for the rounded border effect, you can use hardcoded image files to provide proper anti-aliasing.
The fade effect required gradient image masks and replacing the look and feel which in itself wasn't too difficult either.

BTW I very much enjoyed reading Angelo's post on gradients, this is something that our UI designer has been riding on me to get for a very long time... I'm still trying to figure out a way to integrate this into the whole designer/style paradigm in an easy to use way...

/**
* Allows overriding the form to give it a "rounded border" look and feel
* with a fade out in the bottom in case of scrolling.
*
* @author Shai Almog
*/

public class RoundFadeGlassPane implements Painter {
private Image topLeft;
private Image topRight;
private Image bottomLeft;
private Image bottomRight;
private Form parentForm;

public RoundFadeGlassPane(Form f) {
parentForm = f;
Image blackCorner = Image.createImage(60, 60);

Graphics cornersGraphics = blackCorner.getGraphics();
cornersGraphics.setColor(0);
cornersGraphics.fillRect(0, 0, blackCorner.getWidth(), blackCorner.getHeight());
cornersGraphics.setColor(0xffffff);
cornersGraphics.fillRoundRect(0, 0, 200, 200, 60, 60);

// get the white color since it might be modified when rendering and might not actually be 0xffffff
// e.g. it might be 0xfefefe due to rendering issues
int white = blackCorner.getRGBCached()[blackCorner.getHeight() / 2 * blackCorner.getWidth()];

// remove the white color from the image so only the black corners remain
blackCorner = blackCorner.modifyAlpha((byte)0xff, white);

topLeft = blackCorner;
topRight = topLeft.rotate(90);
bottomRight = topLeft.rotate(180);
bottomLeft = topLeft.rotate(270);
PainterChain.installGlassPane(f, this);
}

public void paint(Graphics g, Rectangle rect) {
g.drawImage(topLeft, 0, 0);
g.drawImage(topRight, parentForm.getWidth() - topRight.getWidth(), 0);
g.drawImage(bottomLeft, 0, parentForm.getHeight() - bottomRight.getHeight());
g.drawImage(bottomRight, parentForm.getWidth() - topRight.getWidth(), parentForm.getHeight() - bottomRight.getHeight());
}
}

/**
* Simple look and feel automatically installing the "round look" for applications
*
* @author Shai Almog
*/

public class RoundFadeLookAndFeel extends DefaultLookAndFeel {
private Object topMask;
private Object bottomMask;
private Image topCache;
private Image bottomCache;

public void bind(Component c) {
if(c instanceof Form) {
new RoundFadeGlassPane((Form)c);
}
}

private Object createFadeMask(boolean direction) {
Image mask = Image.createImage(Display.getInstance().getDisplayWidth(), Display.getInstance().getDisplayHeight() / 10);
Graphics g = mask.getGraphics();
g.setColor(0xffffff);
g.fillRect(0, 0, mask.getWidth(), mask.getHeight());
if(direction) {
g.fillLinearGradient(0, 0xffffff, 0, -5,
mask.getWidth(), mask.getHeight(), false);
} else {
g.fillLinearGradient(0xffffff, 0, 0, 5,
mask.getWidth(), mask.getHeight(), false);
}
return mask.createMask();
}

private Object getTopMask() {
if(topCache == null || topCache.getWidth() !=
Display
.getInstance().getDisplayWidth()) {
topMask = createFadeMask(false);
topCache = Image.createImage(
Display.getInstance().getDisplayWidth(),
Display.getInstance().getDisplayHeight() / 10);
}
return topMask;
}

private Object getBottomMask() {
if(bottomCache == null || bottomCache.getWidth() !=
Display.getInstance().getDisplayWidth()) {
bottomMask = createFadeMask(true);
bottomCache = Image.createImage(
Display.getInstance().getDisplayWidth(),
Display.getInstance().getDisplayHeight() / 10);
}
return bottomMask;
}

public void drawVerticalScroll(Graphics g, Component c,
float offsetRatio, float blockSizeRatio) {
if(offsetRatio + blockSizeRatio < 0.995) {
// we need to draw the fade on the bottom
Object bottom = getBottomMask();
Graphics temp = bottomCache.getGraphics();
temp.translate(0, bottomCache.getHeight() - c.getHeight());
c.paintBackgrounds(temp);
g.drawImage(bottomCache.applyMask(bottom), c.getX(), c.getY() + c.getHeight() - bottomCache.getHeight());
}
if(offsetRatio > 0) {
// we need to draw the fade on the top
Object top = getTopMask();
Graphics temp = topCache.getGraphics();
temp.translate(0, topCache.getHeight() - c.getHeight());
c.paintBackgrounds(temp);
g.drawImage(topCache.applyMask(top), c.getX(), c.getY());
}
super.drawVerticalScroll(g, c, offsetRatio, blockSizeRatio);
}
}

4 comments:

  1. Hi Shai,
    Thanks for mentioning my post. It means a lot to me coming from one of the key developers of LWUIT. :)

    ReplyDelete
  2. Hi Shai,

    not sure if you already know about this, but I notice that in the video, the color depth of the gradients is low and they display banding. This can be easily fixed by changing the the color depth property of the emulator and adding extra '0's to it.

    On WTK 2.5.2 it is at: rootDir/wtklib/devices/DefaultColorPhone and open the properties. Change both colorCount=0x1000000 and alphaLevelCount=0x1000. The color gradients are now displayed in their full glory.

    ReplyDelete
  3. Dear Shai and bharath,

    I am too facing the same problem of setrows of textfield. I want to make more than one line for textfield and i use setrows method on textfield but
    the textfield have only one row

    and also the problem of TextArea too.

    Thank in advance for all
    Hope you will reply soon
    Thank you

    ReplyDelete