ColoredTextAreaOverlayElement         A class to allow Quake 3 style color codes to be inserted into text overlay elements

Introduction

This article presents a class that extends the TextAreaOverlayElement allowing for different colors to be applied to each character in the string individually. The behaviour was designed to mimic the Quake 3 coloring scheme. This should be very easy to integrate into existing code since the interface is virtually identical to the TextAreaOverlayElement interface. For example, consider the following code:

TextAreaOverlayElement *pTextArea =
     (TextAreaOverlayElement*)OverlayManager.createOverlayElement("TextArea", "MyTextArea");
 pTextArea->setCaption("Some plain text");
 ...

Simply needs to be changed to:

TextAreaOverlayElement *pTextArea =
     (TextAreaOverlayElement*)OverlayManager.createOverlayElement("ColoredTextArea", "MyTextArea");
 pTextArea->setCaption("Some ^1red^7 text");
 ...
Note that "
1" and "
7" in the string are color codes. Those familiar with Quake 3 will recognise this. The color that corresponds to each code can be found in the ColoredTextAreaOverlayElement::GetColor function, they are the same as Quake 3 but can easily be changed.


Before this code will work however, the new overlay must be registered using the ColoredTextAreaOverlayElementFactory. Here is some sample code on how to do that:

In header file

ColoredTextAreaOverlayElementFactory* pColoredTextAreaOverlayElementFactory;

Initialisation code

// Register the overlay element
 m_pColoredTextAreaOverlayElementFactory = new ColoredTextAreaOverlayElementFactory();
 OverlayManager::getSingleton().addOverlayElementFactory(pColoredTextAreaOverlayElementFactory);

Deinitialisation code

// This must be called before the ogre root is destroyed or crashes may occur on exit
 delete pColoredTextAreaOverlayElementFactory

Having done this you should also be able to use this class in your overlay scripts (although I haven't tested that).

Similarly to the TextAreaOverlayElement class, a gradient can be applied from the top to the bottom. Instead of specifying an absolute color, a value can be specified instead with the following functions:

void setValueBottom(float Value);
void setValueTop(float Value);


This is essentially the value component of the HSV color.

Here is a screenshot of the class in use (see text at the bottom left):

ColoredTextOverlays.png

Important

Keep in mind that this is not thread safe! We had much trouble to figure out why it was broken: "e used updateColours() from a different thread, never do that!

Code

ColoredTextAreaOverlayElement.h

#pragma once
 
 class ColoredTextAreaOverlayElement : public TextAreaOverlayElement
 {
 public:
     ColoredTextAreaOverlayElement(const String& name);
     ~ColoredTextAreaOverlayElement(void);
 
     void setValueBottom(float Value);
     void setValueTop(float Value);
     void setCaption(const DisplayString& text);
     static DisplayString StripColors(const DisplayString& text);
     static ColourValue GetColor(unsigned char ID, float Value = 1.0f);
 
 protected:
     void updateColours(void);
 
     vector<unsigned char> m_Colors;
     float m_ValueTop;
     float m_ValueBottom;
 };

ColoredTextAreaOverlayElement.cpp

#include "ColoredTextAreaOverlayElement.h"
 
 #define POS_TEX_BINDING 0
 #define COLOUR_BINDING 1
 
 ColoredTextAreaOverlayElement::ColoredTextAreaOverlayElement(const String& name)
 : TextAreaOverlayElement(name)
 , m_ValueTop(1.0f)
 , m_ValueBottom(0.8f)
 {
 }
 
 ColoredTextAreaOverlayElement::~ColoredTextAreaOverlayElement(void)
 {
 }
 
 void ColoredTextAreaOverlayElement::setValueBottom(float Value)
 {
     m_ValueTop = Value;
     mColoursChanged = true;
 }
 
 void ColoredTextAreaOverlayElement::setValueTop(float Value)
 {
     m_ValueBottom = Value;
     mColoursChanged = true;
 }
 
 ColourValue ColoredTextAreaOverlayElement::GetColor(unsigned char ID, float Value)
 {
     switch (ID)
     {
     case 0:
         return ColourValue(0, 0, 0);    // Black
     case 1:
         return ColourValue(Value, 0, 0);    // Red
     case 2:
         return ColourValue(0, Value, 0);    // Green
     case 3:
         return ColourValue(Value, Value, 0);    // Yellow
     case 4:
         return ColourValue(0, 0, Value);    // Blue
     case 5:
         return ColourValue(0, Value, Value);    // Cyan
     case 6:
         return ColourValue(Value, 0, Value);    // Magenta
     case 7:
         return ColourValue(Value, Value, Value);    // White
     }
     return ColourValue::Black;
 }
 
 DisplayString ColoredTextAreaOverlayElement::StripColors(const DisplayString& text)
 {
     DisplayString StrippedText;
     int i;
     for (i = 0; i < (int)text.size()-1; ++i)
     {
         if (text[i] == '^' &&
             text[i+1] >= '0' && text[i+1] <= '7')    // This is a color code, ignore it
         {
             ++i;
         }
         else
         {
             StrippedText.append(1, text[i]);
         }
     }
     // One last character to add because loop went to size()-1
     if (i < (int)text.size())
         StrippedText.append(1, text[i]);
     return StrippedText;
 }
 
 void ColoredTextAreaOverlayElement::setCaption(const DisplayString& text)
 {
     m_Colors.resize(text.size(), 7);
     int i, iNumColorCodes = 0, iNumSpaces = 0;
     for (i = 0; i < (int)text.size()-1; ++i)
     {
         if (text[i] == ' ' || text[i] == '\n')
         {
             // Spaces and newlines are skipped when rendering and as such can't have a color
             ++iNumSpaces;
         }
         else if (text[i] == '^' &&
             text[i+1] >= '0' && text[i+1] <= '7')    // This is a color code
         {
             // Fill the color array starting from this point to the end with the new color code
             // adjustments need to made because color codes will be removed and spaces are not counted
             fill(m_Colors.begin()+i-(2*iNumColorCodes)-iNumSpaces, m_Colors.end(), text[i+1]-'0');
             ++i;
             ++iNumColorCodes;
         }
     }
     // Set the caption using the base class, but strip the color codes from it first
     TextAreaOverlayElement::setCaption(StripColors(text));
 }
 
 void ColoredTextAreaOverlayElement::updateColours(void)
 {
     // Convert to system-specific
     RGBA topColour, bottomColour;
     // Set default to white
     Root::getSingleton().convertColourValue(ColourValue::White, &topColour);
     Root::getSingleton().convertColourValue(ColourValue::White, &bottomColour);
 
     HardwareVertexBufferSharedPtr vbuf = 
         mRenderOp.vertexData->vertexBufferBinding->getBuffer(COLOUR_BINDING);
 
     RGBA* pDest = static_cast<RGBA*>(
         vbuf->lock(HardwareBuffer::HBL_DISCARD) );
 
     for (size_t i = 0; i < mAllocSize; ++i)
     {
         if (i < m_Colors.size())
         {
             Root::getSingleton().convertColourValue(GetColor(m_Colors[i], m_ValueTop), &topColour);
             Root::getSingleton().convertColourValue(GetColor(m_Colors[i], m_ValueBottom), &bottomColour);
         }
 
         // First tri (top, bottom, top)
         *pDest++ = topColour;
         *pDest++ = bottomColour;
         *pDest++ = topColour;
         // Second tri (top, bottom, bottom)
         *pDest++ = topColour;
         *pDest++ = bottomColour;
         *pDest++ = bottomColour;
     }
     vbuf->unlock();
 }


ColoredTextAreaOverlayElementFactory.h

#pragma once
 
 #include <OgreOverlayElementFactory.h>
 #include "ColoredTextAreaOverlayElement.h"
 
 /** Factory for creating TextAreaOverlayElement instances. */
 class ColoredTextAreaOverlayElementFactory : public OverlayElementFactory
 {
 public:
     /** See OverlayElementFactory */
     OverlayElement* createOverlayElement(const String& instanceName)
     {
         return new ColoredTextAreaOverlayElement(instanceName);
     }
     /** See OverlayElementFactory */
     const String& getTypeName() const
     {
         static String name = "ColoredTextArea";
         return name;
     }
 };

Updated version

A slightly updated version can be found there: