Building custom widgets with the Zinzala SDK - page 10

It is only if the button can be pressed, not dimmed, that we will redraw it and restore its state. The button callback will be called in both cases, however; lGood will be false when the button is dimmed.

The last part of handling the user input goes into specific case where the user clicks or taps down on a button, but never releases the mouse button or its finger (when there is no mouse or pen). How could that be? Imagine you are driving a four wheel SUV on a bumpy road (.... okay, trail :) ... Now, you reach for the console in order to stop the playback, but just as you touch the on-screen button, the car rocks and your finger moves off the button. The problem is that the console widget may register the pressed down action on the button, but because you released your finger outside of the widget, the pressed up event will not be processed by the widget.

A simple solution is to use the method OutBound() that is called every time the mouse or finger leaves the widget while still pressed down. When this occurs, we will revert its state and redraw it:

void cDConsoleView::OutBound(uPoint aPoint) { if(iPressedButton < kButtonsCount) { if(iButtons[iPressedButton].iState == kStatePressed) { // revert the state of the button iButtons[iPressedButton].iState = iPrevState; // redraw the button Render(iPressedButton); } iPressedButton = kButtonsCount; } }

Animation

When a button is active, we want to display a pulsating effect on the active button. To render this, we need to redraw the button at a given interval in time. We have used the flag kFlgBeatNeeded in the cDConsoleView base class initialization. Now let's have a look at the implementation of the method Beat(), which will be called at the rate set by the parent window:

void cDConsoleView::Beat() { // if there is an active button if(iActiveButton < kButtonsCount) { if(iButtons[iActiveButton].iState >= kStateActive) {

Because this method will still be called even when there is no button active, we will only draw the animation effect if there is an active button.

// set the state of the button iButtons[iActiveButton].iState += iActiveDir;

The pulsating effect is rendered by incrementing the state of the button from kStateActive to kStateActive2 and then reversing back to kStateActive.

if(iButtons[iActiveButton].iState > kStateActive2) { iButtons[iActiveButton].iState = kStateActive2; iActiveDir = -1; } else if(iButtons[iActiveButton].iState &lt kStateActive) { iButtons[iActiveButton].iState = kStateActive; iActiveDir = 1; }

We use the data member iActiveDir to store the direction we are going. The only thing left to do, is to redraw the button. We simply call the method Render() which use the button state to render it:

// redraw the button Render(iActiveButton); } } }

Later, we will see how to setup the window to beat.

Changing state

All that is left to be done, is SetState(). This method will be used from outside the class in order to change the state of the console. For example, when the user presses the Play button and playback successfully starts, the state should be changed to ePlaying.

Here's the method implementation:

void cDConsoleView::SetState(tConsoleState aState) { if(!iFault) { TheLooperMustBeLocked();

Since we do not know who will be calling this method and when, we need to insure that the looper (the window) is locked. The macro TheLooperMustBeLocked() will check this for us. If it is not locked, an exception will be raised and the application will likely be terminated. This helps us to easily detect situations where we are executing code that should only be executed when the window is locked. Don't forget that the Zinzala SDK, is multithreaded, and that only one thread should be changing the button's state or widget internals at any given time.

switch(aState) { case ePlaying: { // enable the buttons (all of them) for(tUint8 i=0;i<kButtonsCount;i++) iButtons[i].iState = kStateNormal; // change the label of the play button iButtons[kButtonPlay].iLabel = kLabelStop; // and set it as active iButtons[kButtonPlay].iState = kStateActive; iActiveButton = kButtonPlay; break; } case eStopped: { // disable the buttons (all of them) for(tUint8 i=0;i<kButtonsCount;i++) iButtons[i].iState = kStateDimmed; // change the label of the play button iButtons[kButtonPlay].iLabel = kLabelPlay; // and enable it iButtons[kButtonPlay].iState = kStateNormal; // there is no active button iActiveButton = kButtonsCount; break; } case ePaused: { // disable some of the buttons iButtons[kButtonBwd].iState = kStateDimmed; iButtons[kButtonFwd].iState = kStateDimmed; // and set it as active iButtons[kButtonPause].iState = kStateActive; iActiveButton = kButtonPause; break; } case eForward: { // disable some of the buttons iButtons[kButtonBwd].iState = kStateDimmed; iButtons[kButtonPause].iState = kStateDimmed; // and set it as active iButtons[kButtonFwd].iState = kStateActive; iActiveButton = kButtonFwd; break; } case eBackward: { // disable some of the buttons iButtons[kButtonFwd].iState = kStateDimmed; iButtons[kButtonPause].iState = kStateDimmed; // and set it as active iButtons[kButtonBwd].iState = kStateActive; iActiveButton = kButtonBwd; break; } } iState = aState;

Print version

All content © 2004-2007, hexaZen - Vancouver BC, Canada