posted by Jean-Louis Villecroze on Wed 8th Dec 2004 21:40 UTC

"Zinzala, Page 7/11"
Drawing

The method Render() is responsible for drawing the console on the widget. Because the widget is using an offscreen bitmap, we do not have to worry about redrawing any part of the widget that is damaged. This is handled by the cDrawView.

The methods StartDrawing() and EndDrawing() from the class cDrawView are required before and after every block that perform some drawing:

void cDConsoleView::Render(tUint8 aButton,tBool aBackground)
{
   StartDrawing();
	
   if(aButton < kButtonsCount)
   {

We will be using this method to render either the whole console or just a given button. When aButton equal kButtonsCount the whole console will be redrawn.

      // Render only one button
      DrawBitmap(iBStates[iButtons[aButton].iState],iButtons[aButton].iBFrame.GetLeftTop());
      if(iButtons[aButton].iState == kStateDimmed)
         DrawBitmap(iBLabelsD[iButtons[aButton].iLabel],iButtons[aButton].iLFrame.GetLeftTop());
      else
         DrawBitmap(iBLabelsN[iButtons[aButton].iLabel],iButtons[aButton].iLFrame.GetLeftTop());

The method cDrawView::DrawBitmap() draws a bitmap at a given location. We use it here to render the button using the bitmap associated with the button's state, followed by the label of the button.

   }
   else 
   {
      // Render the background
      if(aBackground)
         DrawBitmap(iBackground);
	
      // Render all the button
      for(tUint8 i=0;i<kButtonsCount;i++)
      {
         DrawBitmap(iBStates[iButtons[i].iState],iButtons[i].iBFrame.GetLeftTop());
         if(iButtons[i].iState == kStateDimmed)
            DrawBitmap(iBLabelsD[iButtons[i].iLabel],iButtons[i].iLFrame.GetLeftTop());
         else
            DrawBitmap(iBLabelsN[iButtons[i].iLabel],iButtons[i].iLFrame.GetLeftTop());
      }

When rendering the whole console, we first draw the background bitmap if required, then the 4 buttons with their labels.

   }

   EndDrawing();	
}

In order to render the console for the first time, we implement the method OnScreen(). This method is executed when the widget is put onscreen, following the creation of the parent window:

void cDConsoleView::OnScreen()
{
   cDrawView::OnScreen();
   // Render the whole console (for the first time)
   Render(kButtonsCount,true);
}

User's inputs

Although the user may think that our console contains 4 independent button widgets, it is in fact only one widget. Handling the user actions is going to require the need to know which one of the buttons was acted upon.

When given a position within the widget, the method FindButton() will give us the related button:

tUint8 cDConsoleView::FindButton(uPoint aPoint) const
{
   tUint8 lButton;

   for(lButton=0;lButton<kButtonsCount;lButton++)
   {
      if(iButtons[lButton].iBFrame.Contains(aPoint))
         break;
   }

   return lButton;
}

Its algorithm is simple. We loop over all the buttons and check if aPoint is contained in the button frame. If none of the buttons contain the position being tested, the method will return kButtonsCount. Although simple, there is one little issue ... What if the buttons are circular?

Because we are using the rectangular frame of the button, if we were to tap in a corner of the frame, which is dearly outside of the button, it will still act as if we clicked in the center of the button. Can we live with it ? If our UI is to be used on a touch-screen in a moving vehicle, it wouldn't matter much since the user's input will not be too precise. However, if the input is triggered by a pen, then we should do something to improve it.

One solution, on top of what we've already done, is to use the button's bitmap to find out if the position where the user has clicked, is transparent or not. If it is transparent, then we are outside of the button.

Here's the improved method:

tUint8 cDConsoleView::FindButton(uPoint aPoint) const
{
   tUint8 lButton;

   for(lButton=0;lButton<kButtonsCount;lButton++)
   {
      if(iButtons[lButton].iBFrame.Contains(aPoint))
      {
         tUint8 lIndex;
         tUint8 lTrans;
         uPoint lPoint;

         // calculate position within the button frame
         lPoint.iX = aPoint.iX - iButtons[lButton].iBFrame.iLeft; 
         lPoint.iY = aPoint.iY - iButtons[lButton].iBFrame.iTop; 
         // get transparent color index
         iBStates[kStateNormal]->GetTransparent(lTrans);
         // get pixel color index
         iBStates[kStateNormal]->GetPixelColor(lPoint.iX,lPoint.iY,&lIndex);
         // if the color is transparent, then we are outside of the button
         if(lIndex != lTrans)
            break;
      }
   }

   return lButton;
}

Now, there are two main events that we are going to handle: the user presses down on a button, and the user releases the button. The first one will be handled in the method MouseDown(), the second in MouseUp():

void cDConsoleView::MouseDown(uPoint aPoint)
{
   Pressed(FindButton(aPoint));
}

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

      iPressedButton = kButtonsCount;
   }
}

When the user clicks, taps or presses somewhere on the console, the method MouseDown() will be called. This method will execute Pressed() with the identifier of the button pressed. If a button was indeed pressed, we will mark the button as pressed. It is only when the user releases the button, that we will invoke it from MouseUp(). If the button is released outside of its boundary, we will not invoke it.

Here is the method Pressed():

void cDConsoleView::Pressed(tUint8 aButton)
{
   // if the mouse event was on a button
   if(aButton < kButtonsCount)
   {
      iPressedButton = aButton;
      // if the button is not dimmed
      if(iButtons[aButton].iState == kStateNormal || iButtons[aButton].iState >= kStateActive)
      {
         // set the button as pressed
         iPrevState = iButtons[aButton].iState;
         iButtons[aButton].iState = kStatePressed;
         // redraw the button
         Render(aButton);
      }
   }
}

Whether the button is dimmed or not, we mark it as pressed. However, we only really set it as pressed and redraw it, if it is not dimmed.

The purpose of the method Invoked() is to accept and acknowledge the user action on a given button. In order to improve the illusion of a physical button, we are going to redraw the button with a bit of a delay. That way it will look pressed a little bit longer. Here it goes:

void cDConsoleView::Invoked()
{
   tBool lGood = false;

   if(iPressedButton < kButtonsCount)
   {
      if(iButtons[iPressedButton].iState == kStatePressed)
      {
         // delay a bit to have a better effect
         sSnooze::SomeMS(kDelayPressed);

         // restore the previous state of the button
         iButtons[iPressedButton].iState = iPrevState;
         // redraw the button
         Render(iPressedButton);

         lGood = true;
      }
		
      // call the hook method linked to the pressed button
      switch(iPressedButton)
      {
         case kButtonPlay:
         {
            if(iButtons[kButtonPlay].iLabel == kLabelStop)
               StopPressed(lGood);
            else
               PlayPressed(lGood);
            break;
         }
         case kButtonPause:
         {
            PausePressed(lGood);
            break;
         }
         case kButtonBwd:
         {
            BwdPressed(lGood);
            break;
         }
         case kButtonFwd:
         {
            FwdPressed(lGood);
            break;
         }
      }
	
      iPressedButton = kButtonsCount;
   }
}

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() which 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;
   }
}

Table of contents
  1. "Zinzala, Page 1/11"
  2. "Zinzala, Page 2/11"
  3. "Zinzala, Page 3/11"
  4. "Zinzala, Page 4/11"
  5. "Zinzala, Page 5/11"
  6. "Zinzala, Page 6/11"
  7. "Zinzala, Page 7/11"
  8. "Zinzala, Page 8/11"
  9. "Zinzala, Page 9/11"
  10. "Zinzala, Page 10/11"
  11. "Zinzala, Page 11/11"
e p (0)    12 Comment(s)

Technology White Papers

See More