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).
{
// 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.
{
// 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.)
{
// 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.
{
// 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;
}