Thursday, May 31, 2007

Mea Culpa: _run is not _link

I mentioned in Lesson 7 that the _run() API is essentially identical to the _link() API. That is incorrect.

When an application is _run(), it pushes the current application down the application stack and disables it. The _run() application is then loaded and executed.

When an application is _link(), it becomes part of the currently running application.

The difference between these two is that all functions of the previous application are disabled when a subprogram is _run(). A _link()ed partition is loaded into memory as part of the current application, so all features of the current application are still enabled.

The _replace() API falls into the same category as _run() in that it disables the currently running application. The difference is that when the _run() application completes, the "pushed" application is re-activated (from initialization, not from where it left off). A _replace()ed application is pushed out of the application stack forever.

Sorry for the bad information.

Adjustable User Interface

Once you start working with UJML and targetting various devices, one thing becomes abundantly clear: Screen sizes are never the same. The screen size may be VGA (640x480), but most likely you won't be able to use the entire screen. What matters to you, the application developer, is not the total screen size, but the "usable screen size". This is the screen area that can be updated by the program.

In order to determine the screen size, UJML provides the API _getIntProperty(). This API takes an integer parameter defined in the DTD file and returns the appropriate information from the system. The two parameters that concern screen size are _PROPERTY_INT_SCREEN_WIDTH and _PROPERTY_INT_SCREEN_HEIGHT. By passing these parameters to _getIntProperty, we can retrieve the width and height of the usable screen area.

This code is boilerplate for the vast majority of applications:

mScrWidth = _getIntProperty( &_PROPERTY_INT_SCREEN_WIDTH; );
mScrHeight = _getIntProperty( &_PROPERTY_INT_SCREEN_HEIGHT; );

From this information, positioning objects on the screen is simply a matter of calculating offsets from the top, bottom, left, and right. If you wish to place an object flush to the right-hand side of the screen, you set its <x> parameter to be mScrWidth - objectwidth. Likewise, placing an object relative to the bottom of the screen means setting its <y> parameter to be the object's height subtracted from mScrHeight.

Knowing this simple concept, it is possible to draw anything, anywhere on the screen (or even off the screen!) However, UJML has some pitfalls which are easily worked around, yet frustrating if you don't realize the problem.

One of the first applications of screen positioning is the use of percentage offsets. Say you want to fill a certain area of the screen with _COLOR_RED. What you may find is that the area you are trying to paint never appears. The problem is a result of UJML's lack of floating point numbers. You can't say "mScrWidth * 0.42" and expect to get 42% of the screen width. UJML only provides integer arithmetic, so in order to get 42% of the screen width, you must first multiply the screen width by 42, then divide it by 100.

mPosX = mScrWidth * 42 / 100; // OK
mPosX = mScrWidth / 100 * 42; // NG: this will lose signficant digits

Keep in mind that integer arithmetic truncates, so don't divide before multiplying!

Another issue that crops up a lot is overlapping percentages. Since integer arithmetic truncates fractional values, sometimes your calculated areas will overlap each other by a pixel. In most cases, this is not a big deal, but for those of us who don't want our applications looking like they were developed by a sysadmin, it makes sense to be aware of this problem.

A decent solution is to use offsets based on the values returned by the percentage calculations. While this may have the side effect of moving the pixel problem off to the edge of the screen, those are pixels that not many people notice anyway. Better to have a row or column of black pixels on the edge of the screen than to have uneven display in the middle.

To implement this, just keep track of where you are positioning items. Then, instead of basing the position of the next item on the x,y origin, base it on the offset from the item immediately adjacent to it.

mPosX = 0; // this is the first object, based on the x,y origin
mPosW = mScrWidth * 42 / 100;
mPosX2 = mPosX + mPosW; // This is the second object, dynamically positioning itself relative to the first object

In a nutshell, that's it. Positioning items on the screen is relatively easy, and UJML provides excellent drawing capabilities for the application developer. Since we can't depend on the screen size of our devices to always be the same, it is important that our applications adapt to the screen size we are given.