Thursday, March 26, 2009

Painting custom commands

Commands should be the last elements drawn on the screen. This way the user will always know his options for that screen.
We are using the height of commands area as the default font height, so we need to override getHeight method:
private boolean hasCommands () {
   return (this.leftCommand != null) || (this.rightCommand != null);
}
public int getHeight () {
   int height = super.getHeight();
   
   if (hasCommands()) 
      height -= Font.getDefaultFont().getHeight();

   return height;
}
A simple way to implement command drawing is:
protected paint (Graphics g) {
   // draw other elements

   if (hasCommands()) {
      int commandHeight = Font.getDefaultFont().getHeight();

      // clear command area
      g.setColor(0xffffff); // white
      g.fillRect(0, super.getHeight() - commandHeight, getWidth(), commandHeight);
      g.setColor(0); // black
      g.setFont(Font.getDefaultFont());
      if (this.leftCommand != null) {
         g.drawString(this.leftCommand.getLabel(), 0, super.getHeight(), Graphics.LEFT | Graphics.BOTTOM);
      }
      if (this.rightCommand != null) {
         g.drawString(this.rightCommand.getLabel(), getWidth(), super.getHeight(), Graphics.RIGHT | Graphics.BOTTOM);
      }
   }
}
At LWUIT there is a simple implementation as this at DefaultLookAndFeel class.
A better approach is to give visual feedback on keyPressed calls, for example, changing the font color before painting a command.
Lets define two new attributes and add keyPressed method:
private boolean leftCommandPressed;
private boolean rightCommandPressed;
protected void keyPressed (int keyCode) {
   if (keyCode == this.leftCommandKey && this.leftCommand != null) {
      this.leftCommandPressed = true;
      this.repaint();
   } else if (keyCode == this.rightCommandKey && this.rightCommand != null) {
      this.rightCommandPressed = true;
      this.repaint();
   }
}
Now we change paint method to set command color according to these new attributes.
protected paint (Graphics g) {
   if (hasCommands()) {
      if (this.leftCommand != null) {
         if (this.leftCommandPressed) {
            g.setColor(0x080808); // gray
         } else {
            g.setColor(0); // black
         }
         g.drawString(this.leftCommand.getLabel(), 0, super.getHeight(), Graphics.LEFT | Graphics.BOTTOM);
      }
      if (this.rightCommand != null) {
         if (this.rightCommandPressed) {
            g.setColor(0x080808); // gray
         } else {
            g.setColor(0); // black
         }
         g.drawString(this.rightCommand.getLabel(), getWidth(), super.getHeight(), Graphics.RIGHT | Graphics.BOTTOM);
      }
   }
}
And change keyReleased to reset the attributes:
protected void keyReleased (int keyCode) {
   if (this.commandListener != null){
      if (keyCode == this.leftCommandKey && this.leftCommand != null) {
         this.leftCommandPressed = false;
         this.commandListener.commandAction(this.leftCommand, this);
      } else if (keyCode == this.rightCommandKey && this.rightCommand != null) {
         this.rightCommandPressed = false;
         this.commandListener.commandAction(this.rightCommand, this);
      }
   }
}

Sunday, March 1, 2009

Custom commands

We presented ways to customize title and content with Canvas, but now it is time to also treat commands.
For our code lets define the following attributes:
private int leftCommandKey, rightCommandKey;
A good example of adaptive code to identify command keys can be found at LWUIT class com.sun.lwuit.Implementation, method setKnownSoftKeyCodes.
Below is an adapted snippet to treat Nokia and Motorola commands:


try {
Class.forName("com.nokia.mid.ui.FullCanvas");
this.leftCommandKey = -6;
this.rightCommandKey = -7;
return;
} catch (ClassNotFoundException _ex) { }
try {
Class.forName("com.motorola.phonebook.PhoneBookRecord");
this.leftCommandKey = -21;
this.rightCommandKey = -22;
return;
} catch (ClassNotFoundException _ex) { }

As there are more manufacturers you may keep adding try/catch clauses for specific classes.
One way to avoid this lot of try/catch blocks is the final loop in the method:


boolean leftInit = false;
boolean rightInit = false;
for(int i = -127; i <= 0 && !leftInit && !rightInit; i++) {
try {
if (getKeyName(i).indexOf("1") >= 0) {
this.leftCommandKey = i;
leftInit = true;
}
if (getKeyName(i).indexOf("2") >= 0) {
this.rightCommandKey = i;
rightInit = true;
}
} catch (Exception ex) { }
}

This only works because Implementation class extends Canvas and can use getKeyName method, so your class must extend Canvas too.
From getKeyName javadoc:
"Gets an informative key string for a key. The string returned will resemble the text physically printed on the key. This string is suitable for displaying to the user. For example, on a device with function keys F1 through F4, calling this method on the keycode for the F1 key will return the string "F1". A typical use for this string will be to compose help text such as "Press F1 to proceed."
This method will return a non-empty string for every valid key code.
There is no direct mapping from game actions to key names. To get the string name for game action GAME_A, the application must call
getKeyName(getKeyCode(GAME_A))"
From the loop above we can deduce that commands are SOFT keys and that number 1 is the left and number 2 is the right.
We will try to keep the same behavior and call CommandListener.commandAction when one of these keys is released, but first we need to know which Command will be used for each soft key.
Commands are set with Displayable.addCommand and the listener is set with Displayable.setCommandListener.
As there is no getter method for commands or listener it is necessary to override them.


private Command leftCommand, rightCommand;
public void addCommand (Command cmd) {
if (this.leftCommand == null) {
this.leftCommand = cmd;
} else if (rightCommand == null) {
this.rightCommand = cmd;
}
}
private CommandListener commandListener;
public void setCommandListener (CommandListener l) {
this.commandListener = l;
}

The final piece of code:


protected void keyReleased (int keyCode) {
if (this.commandListener != null){
if (keyCode == this.leftCommandKey && this.leftCommand != null) {
this.commandListener.commandAction(this.leftCommand, this);
} else if (keyCode == this.leftCommandKey && this.rightCommand != null) {
this.commandListener.commandAction(this.rightCommand, this);
}
}
}


Related topics: