Game Development Community

Multi-line ToolTip (automatically wraps tool tip text)

by Ryan Deluz · in Torque Game Engine · 02/19/2008 (3:55 pm) · 1 replies

The following changes to guiControl.cc allow for pop-up tool tips to automatically wrap. This is not the most efficient code, but it does not store any extra data, so it only takes up memory/processor power while the tip is actually displayed, and also requires a minimum of changes. This is based on 1.5.2, but is probably both forwards and backwards compatible to other versions:

The line-wrap code does not break words in two, so it should look quite nice.

All changes are to guiControl.cc (engine/gui/core)

place this near the top of guiControl.cc, below the header files
TOOLTIPMLMAXSIZE is the greatest number of characters of the total tool tip (not per line)

//SOMATIC VISION CHANGES START
//structure to hold strings without allocating/deallocating memory
#define TOOLTIPMLMAXSIZE 2048
struct ToolTipML {
char text[TOOLTIPMLMAXSIZE];
};
//SOMATIC VISION CHANGES END


CONTINUE TO NEXT POST TO FINISH THE REST OF THE CHANGES

#1
02/19/2008 (3:55 pm)
Change renderToolTip function as follows, adding/modifying the places where it says //SOMATIC VISION CHANGES START and //SOMATIC VISION CHANGES END

bool GuiControl::renderTooltip(Point2I cursorPos, const char* tipText )
{
...
textBounds.x = textWidth+8;
textBounds.y = font->getHeight() + 4;

//SOMATIC VISION CHANGES START
//IF THE TEXT IS WIDER THAN THE SCREEN, SUBDIVIDE IT IN TWO UNTIL IT IS NO LONGER WIDER
//recalculate total height, and make sure to render all strings in correct locations
bool isML = false;
Vector toolTipML;
if (textWidth >= screensize.x && screensize.x > 400) {

isML = true;
U32 stringLength; //size of the full string
U32 segmentLength; //size of the first half
Vector tempToolTip;

//start with just one big string
ToolTipML originalString;
dStrcpy(originalString.text, renderTip);
toolTipML.push_back( originalString );

//split in two until no longer too big
while (font->getStrWidth(toolTipML[0].text) >= screensize.x-20 && dStrlen(toolTipML[0].text) < TOOLTIPMLMAXSIZE) { //a little margin as we only check the first element

//copy toolTipML to tempToolTip
tempToolTip.clear();
for (U32 i = 0; i < toolTipML.size(); i++) {
tempToolTip.push_back(toolTipML[i]);
}

//empty the toolTipML
toolTipML.clear();

//divide all tempToolTip elements and put back into toolTip
for (U32 i = 0; i < tempToolTip.size(); i++) {
stringLength = dStrlen(tempToolTip[i].text); //size of full string
segmentLength = stringLength/2; //size of the first half
//find first space after segmentLength
for (U32 j = segmentLength; j < stringLength-1; j++) {
if (tempToolTip[i].text[j] == ' ' && tempToolTip[i].text[j+1] != ' ') {
//break at first space after segmentLength
segmentLength = j+1;
break;
}
}
ToolTipML line1;
ToolTipML line2;

//its possible there were no spaces, so if we don't have a second segment break and just use this last segment as is
if (segmentLength == stringLength-1) {
toolTipML.push_back(tempToolTip[i]); //use that segment as is
break;
}

for (U32 j = 0; j < stringLength; j++) {
if (j >= segmentLength) {
//second half of string
line2.text[j-segmentLength] = tempToolTip[i].text[j];
} else {
//first half of string
line1.text[j] = tempToolTip[i].text[j];
}
}

line1.text[segmentLength] = '\0';
line2.text[(stringLength-segmentLength)] = '\0';
toolTipML.push_back( line1 );
toolTipML.push_back( line2 );
}
}

//find the longest line
textWidth = font->getStrWidth(toolTipML[0].text);
if (toolTipML.size() > 0) {
for (U32 i = 1; i < toolTipML.size(); i++) {
if (font->getStrWidth(toolTipML[i].text) > textWidth) {
textWidth = font->getStrWidth(toolTipML[i].text);
}
}
}

//new text bounds
textBounds.x = textWidth+8;
textBounds.y = (font->getHeight() * toolTipML.size()) + 4;
}
//SOMATIC VISION CHANGES END

// Check position/width to make sure all of the tooltip will be rendered
// 5 is given as a buffer against the edge
if (screensize.x < offset.x + textBounds.x + 5)
offset.x = screensize.x - textBounds.x - 5;

...

// Draw the text centered in the tool tip box
dglSetBitmapModulation( mTooltipProfile->mFontColor );
Point2I start;

//SOMATIC VISION CHANGES START
if (!isML) {
start.set( ( textBounds.x - textWidth) / 2, ( textBounds.y - font->getHeight() ) / 2 );
dglDrawText( font, start + offset, renderTip, mProfile->mFontColors );
} else {
start.set( ( textBounds.x - textWidth) / 2, ( textBounds.y - (font->getHeight()*toolTipML.size()) ) / 2 );
for (U32 i = 0; i < toolTipML.size(); i++) {
dglDrawText( font, start + offset, toolTipML[i].text, mProfile->mFontColors );
offset.y += font->getHeight();
}
toolTipML.clear();
}
//SOMATIC VISION CHANGES END

dglSetClipRect( oldClip );
return true;
}