Table Top Framework Complex Image Component

<< Back to the Tabletop Framework page

Building a complex image Component

This tutorial gives a brief overview how to build a more complex image Component on top of the simple one. By inheritance we get access to all the functionality of the simple image. The complex image features button capabilities, which means that certain areas of the image can have functionality attached, e.g., for resizing the image. This is realized via a local buffer.

It is very useful (if not even necessary) to have a good understanding
of the framework architecture from reading the information provided in the

Getting Started section.

Overview

As a lot of functionality is already implemented by the simple image, this Component has to focus on the part of the interface that enables button capabilities.

Which interfaces to implement?

To use active buffer(s), we have to take care that they are created and provide a method that fills them automatically with the right values.


  virtual void createActiveBuffers(); // builds the stack of active buffers
  virtual void renderInActiveBuffers(); // fills the active buffers with the pre-computed values

Button capabilites on the image are realized by two methods. One of them can be used to check where on the image the user interacted, the other one to check where the user released the image.


  virtual unsigned int hitLocalBuffer(unsigned int globalX, unsigned int globalY);
  virtual unsigned int releaseLocalBuffer(unsigned int globalX, unsigned int globalY);

What can be reused from the simple image

...

What's new

Active buffers

First, we provide a method to create a local buffer from known attributes getBufferWidth() etc. via the framework core. The created buffer is tagged for later retrieval and to allow for connection by children (here not the case as we do not build a Composite).

void ImageComplexLeafGL::createActiveBuffers()
{
  // use the controller (= Visualization) to create a buffer of type unsigned int
  BufferTemplate<unsigned int>* buttonBuffer = getController()->createNewUIntImageBuffer(getBufferWidth(), getBufferHeight());
  // assign a tag to this buffer to be able to identify it
  setActiveBuffer(BUTTON_1D_UINT, buttonBuffer);
}

A generic method that puts values into our active buffers is implemented. It makes use of attributes such as the buffer size of the local buffer stack to adapt to changes.

void ImageComplexLeafGL::renderInActiveBuffers()
{
  // retrieve our active buffer with the required tag
  BufferTemplate<unsigned int>* buttonBuffer = (BufferTemplate<unsigned int>*) getActiveBuffer(BUTTON_1D_UINT);
  // initialize the buffer contents with no relevant value
  buttonBuffer->fillEntireBuffer(NO_BUTTON);

  // the bottom right corner of the buffer and, therefore, the image is labeled for resizing
  for (int i = (int)(getBufferWidth() - getBufferWidth()/8); i < (int)getBufferWidth(); ++i)
  {
    for (int j = 0; j < (int)getBufferHeight()/8; ++j)
    {
      // set the buffer value to resize
      buttonBuffer->setValue(i,j,RESIZE_BUTTON);
    }
  }
}

We can use whatever values we like for these tasks. To have a unified approach and to make changes easier, an ENUM is used, which is strongly recommend both for all button values and also for all buffer tags.

Local buffer processing (for buttons)

As discussed in the thesis, if interaction with a Component occurs, it is first checked for a hit on the local buffer to catch any high-priority interaction such as buttons. For this, the following method is re-implemented. (It is important that it return by default NO_BUTTON which allows interaction to take place without interruption by a local hit.)

unsigned int ImageComplexLeafGL::hitLocalBuffer(unsigned int globalX, unsigned int globalY)
{
  // processing that is done if the device is pressed for the first time over this component
  if(!buttonFlag && !getParent())
  {
    // remember we already touched this local buffer
    buttonFlag = true;

    // retrieve the button buffer
    BufferTemplate<unsigned int>* buttonBuffer = (BufferTemplate<unsigned int>*) getActiveBuffer(BUTTON_1D_UINT);

    // what are the local coordinates of the global position the user touched?
    double localX, localY;
    getLocalCoordinates(globalX, globalY, &localX, &localY);

    // coordinates are valid
    if ( (localX > -1) && (localY > -1) )
    {   
      // transform local coordinates into buffer coordinates
      // (the buffer does not necessarily has the same size as the geometry)
      unsigned int bufferX = this->getBufferXCoordinate(localX);
      unsigned int bufferY = this->getBufferYCoordinate(localY);     

      // look up the value in the local buffer
      lastButtonId = buttonBuffer->getValue(bufferX, bufferY);

      // we want to resize dynamically (during interaction so the user sees it)
      // therefore, we check for the resize value
      if(lastButtonId == RESIZE_BUTTON)
      {
        // calculate the distance we normalize the radius with
        baseDistance = sqrt(pow((_pos[0]-globalX),2) + pow((_pos[1]-globalY),2));
        // adapt to the previous radius (otherwise ugly effects if resizing an already resized image)
        baseDistance /= getRadiusWidth();
        // set flag so we know we are resizing
        resizeFlag = true;
      }

    } else {
      // reset all
      lastButtonId = NO_BUTTON;
    }
  } 

  // dynamic resize processing
  if(resizeFlag)
  {
    double newDistance = sqrt(pow((_pos[0]-globalX),2) + pow((_pos[1]-globalY),2));

    // normalize to the base and set new radius
    double newRadius = newDistance / baseDistance;
    this->setRadius(newRadius);
  }

  // information for the core:
  // if NO_BUTTON is returned, the Component gets moved and rotated as usual
  // if anything else is returned, only this method gets executed and rest is skipped
  return lastButtonId;
}

Interaction of this kind is generally twofold. The user does something (which is handled in the hit method above) and after she is finished, some more things (mostly internally) have to be done. This is realized with the following method, which is called when a Component is dropped.

unsigned int ImageComplexLeafGL::releaseLocalBuffer(unsigned int globalX, unsigned int globalY)
{
  // a value was retrieved before
  if(buttonFlag)
  {
    // reset the flags
    buttonFlag = false;
    resizeFlag = false;
    baseDistance = (double)getBaseWidth()/2;

    // do post-processing according to previously touched button
    switch(lastButtonId)
    {
       // just an example, there is no DELETE_BUTTON at the moment
       // ...but there could be
      case DELETE_BUTTON:
         // if there was a DELETE_BUTTON touched, we delete this Component
        getController()->deleteComponent(this);
        break;
    }

  } else {
    lastButtonId = NO_BUTTON;
  }

  return lastButtonId;
}