Game Development Community

dev|Pro Game Development Curriculum

Fading Transparent GuiTextCtrl

by Rubes · 10/08/2007 (10:22 am) · 9 comments

Download Code File

During development, I found I had the need to use a GuiTextCtrl to display a simple text string, but I wanted the text to be able to fade in from transparency and back out again, on command. The only way I could figure out how to do this in script was to modify the alpha value of the GuiTextCtrl's profile, but this was staggeringly slow. An engine solution was needed.

Fortunately, someone had already done something very similar to this. Jeff Parry's excellent "Fading transparent bitmap control" resource does this with bitmap images, so the code was already essentially there. His resource did not provide script functions to trigger the fading in or fading out process, so I modified it to include this functionality.

I thought originally to create a new GuiControl for this purpose, but instead I just modify the GuiTextCtrl itself to allow fading if desired. If fading is not desired, it will work the same as a normal GuiTextCtrl, and this is set as the default behavior.

To use this, just download the GuiTextCtrl.h and .cc files in the archive and drop them into your project, replacing the old GuiTextCtrl.h and .cc files. NOTE: As usual, you should test this out on a clean install of TGE 1.5.x before messing with your own project! You have been duly warned.

Below is the code for GuiTextCtrl.h, with the modifications for this resource shown in bold:

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//
// Modifications for text fading by Michael Rubin (MAR)
//-----------------------------------------------------------------------------

#ifndef _GUITEXTCTRL_H_
#define _GUITEXTCTRL_H_

#ifndef _GFONT_H_
#include "dgl/gFont.h"
#endif
#ifndef _GUITYPES_H_
#include "gui/core/guiTypes.h"
#endif
#ifndef _GUICONTROL_H_
#include "gui/core/guiControl.h"
#endif

class GuiTextCtrl : public GuiControl
{
private:
   typedef GuiControl Parent;

public:
   enum Constants { MAX_STRING_LENGTH = 1024 };

[b]   // MAR: added for text fadein/fadeout
   bool startFadein;
   bool fadeinDone;
   U32 fadeinTime;
   U32 fadeinStartTime;
   bool startFadeout;
   bool fadeoutDone;
   U32 fadeoutTime;
   U32 fadeoutStartTime;
   bool fadeCtrl;
   // MAR: end[/b]

protected:
   StringTableEntry mInitialText;
   StringTableEntry mInitialTextID;
   UTF8 mText[MAX_STRING_LENGTH + 1];
   S32 mMaxStrLen;   // max string len, must be less then or equal to 255
   Resource<GFont> mFont;

public:

   //creation methods
   DECLARE_CONOBJECT(GuiTextCtrl);
   GuiTextCtrl();
   static void initPersistFields();

   //Parental methods
   bool onAdd();
   virtual bool onWake();
   virtual void onSleep();

   //text methods
   virtual void setText(const char *txt = NULL);
   virtual void setTextID(S32 id);
   virtual void setTextID(const char *id);
   const char *getText() { return (const char*)mText; }

   // Text Property Accessors
   static bool setText(void* obj, const char* data) { static_cast<GuiTextCtrl*>(obj)->setText(data); return true; }
   static const char* getTextProperty(void* obj, const char* data) { return static_cast<GuiTextCtrl*>(obj)->getText(); }


   void inspectPostApply();
   //rendering methods
   void onPreRender();
   void onRender(Point2I offset, const RectI &updateRect);
   void displayText( S32 xOffset, S32 yOffset );

   //Console methods
   const char *getScriptValue();
   void setScriptValue(const char *value);
   
[b]   // MAR: added for text fadein/fadeout
   bool GuiTextCtrl::beginFadein();
   bool GuiTextCtrl::beginFadeout();
   // MAR: end [/b]
};

#endif //_GUI_TEXT_CONTROL_H_

Below are the changes to GuiTextCtrl.cc. In GuiTextCtrl::GuiTextCtrl(), the lines in bold are added (note that the default for fadeCtrl is false, which means the default behavior of the GuiTextCtrl is to not perform fading in or out):

GuiTextCtrl::GuiTextCtrl()
{
   //default fonts
   mInitialText = StringTable->insert("");
   mInitialTextID = StringTable->insert("");
   mText[0] = '[[60c21c21625e0]]';
   mMaxStrLen = GuiTextCtrl::MAX_STRING_LENGTH;
   
[b]   // MAR: added for text fadein/fadeout
   startFadein       = false;
   fadeinDone        = false;
   fadeinTime        = 1000;
   fadeinStartTime   = 0;
   startFadeout      = false;
   fadeoutDone       = false;
   fadeoutTime       = 1000;
   fadeoutStartTime  = 0;
   fadeCtrl          = false;   // distinguishes between controls that should or should not fade
   // MAR: end[/b]
}

Later, in GuiTextCtrl::InitPersistFields():

void GuiTextCtrl::initPersistFields()
{
   Parent::initPersistFields();
   //addField( "text",       TypeCaseString,  Offset( mInitialText, GuiTextCtrl ) );
   addProtectedField("text", TypeCaseString, Offset(mInitialText, GuiTextCtrl), setText, getTextProperty, "");
   addField( "textID",     TypeString,      Offset( mInitialTextID, GuiTextCtrl ) );
   addField( "maxLength",  TypeS32,         Offset( mMaxStrLen, GuiTextCtrl ) );
   
[b]   // MAR: added for text fadein/fadeout
   addField("startFadein",   TypeBool,  Offset(startFadein,  GuiTextCtrl));
   addField("fadeinDone",    TypeBool,  Offset(fadeinDone,   GuiTextCtrl));
   addField("fadeinTime",    TypeS32,   Offset(fadeinTime,   GuiTextCtrl));
   addField("startFadeout",  TypeBool,  Offset(startFadeout, GuiTextCtrl));
   addField("fadeoutDone",   TypeBool,  Offset(fadeoutDone,  GuiTextCtrl));
   addField("fadeoutTime",   TypeS32,   Offset(fadeoutTime,  GuiTextCtrl));
   addField("fadeCtrl",      TypeBool,  Offset(fadeCtrl,     GuiTextCtrl));
   // MAR: end[/b]
}

The main change is to GuiTextCtrl::onRender(). The entire function is replaced with the code below:

[b]void GuiTextCtrl::onRender(Point2I offset, const RectI &updateRect)
{
   // MAR: this code modified to allow text fadein/fadeout
   
   U32 current = Platform::getRealMilliseconds();
   U32 elapsed;
   U32 alpha;
   
   if (!fadeCtrl) {
      // we're not supposed to fade in or out, so just render us normally
      dglSetBitmapModulation( mProfile->mFontColor );
      renderJustifiedText(offset, mBounds.extent, (char*)mText);
      
      //render the child controls
      renderChildControls(offset, updateRect);
      return;
   }
   
   if (!startFadein) {
      // haven't started the fadein sequence yet, so should not be visible
      ColorI color(mProfile->mFontColor.red, mProfile->mFontColor.green, mProfile->mFontColor.blue, 0);
      dglSetBitmapModulation(color);
      renderJustifiedText(offset, mBounds.extent, (char*)mText);
      renderChildControls(offset, updateRect);
      return;
   }
   
   if (!startFadeout) {
      // we've started the fadein sequence, but we haven't started the fadeout sequence yet
      
      if (fadeinDone) {
         // we've already finished fading in, so just display at full alpha
         ColorI color(mProfile->mFontColor.red, mProfile->mFontColor.green, mProfile->mFontColor.blue, 255);
         dglSetBitmapModulation(color);
         renderJustifiedText(offset, mBounds.extent, (char*)mText);
         renderChildControls(offset, updateRect);
         return;
      }
      else {
         // we're still fading in
         elapsed = current - fadeinStartTime;
         alpha = 255 * (F32(elapsed) / F32(fadeinTime));
         if (alpha > 255) {
            alpha = 255;
         }
         if (elapsed > fadeinTime) {
            fadeinDone = true;
         }
         ColorI color(mProfile->mFontColor.red, mProfile->mFontColor.green, mProfile->mFontColor.blue, alpha);
         dglSetBitmapModulation(color);
         renderJustifiedText(offset, mBounds.extent, (char*)mText);
         renderChildControls(offset, updateRect);
         return;
      }
   }
   else {
      // we've started the fadeout sequence
      
      if (fadeoutDone) {
         // we're already finished fading out, so not much else left to do
         startFadein = false;
         startFadeout = false;
         fadeinDone = false;
         fadeoutDone = false;
         ColorI color(mProfile->mFontColor.red, mProfile->mFontColor.green, mProfile->mFontColor.blue, 0);
         dglSetBitmapModulation(color);
         renderJustifiedText(offset, mBounds.extent, (char*)mText);
         renderChildControls(offset, updateRect);
         return;
      }
      else {
         // we're still fading out
         elapsed = current - fadeoutStartTime;
         alpha = 255 * (F32(elapsed) / F32(fadeoutTime));
         if (alpha > 255) {
            alpha = 255;
         }
         if (elapsed > fadeoutTime) {
            fadeoutDone = true;
         }
         ColorI color(mProfile->mFontColor.red, mProfile->mFontColor.green, mProfile->mFontColor.blue, 255-alpha);
         dglSetBitmapModulation(color);
         renderJustifiedText(offset, mBounds.extent, (char*)mText);
         renderChildControls(offset, updateRect);
         return;
         }
      }

   // MAR: end
}[/b]

Finally, these last methods are added at the end of the file to trigger the fadein or fadeout, and to provide script hooks for these functions:

[b]//-----------------------------------------------------------------------------
// MAR: this code added for text fadein/fadeout

bool GuiTextCtrl::beginFadein()
{
   fadeinStartTime = Platform::getRealMilliseconds();
   startFadein = true;
}

bool GuiTextCtrl::beginFadeout()
{
   fadeoutStartTime = Platform::getRealMilliseconds();
   startFadeout = true;
}

ConsoleMethod( GuiTextCtrl, beginFadein, void, 2, 2, ""
               "Start the fade-in sequence.")
{
   object->beginFadein();
}

ConsoleMethod( GuiTextCtrl, beginFadeout, void, 2, 2, ""
               "Start the fade-out sequence.")
{
   object->beginFadeout();
}

// MAR: end
//-----------------------------------------------------------------------------[/b]

That's all there is to it. To use the new GuiTextCtrl, use it the same way as you would any other GuiTextCtrl. If you want it to be able to fade in and out, set its fadeCtrl variable to true, and then specify desired values for fadeinTime and fadeoutTime, as below:

new GuiTextCtrl(IntroCredit) {
      canSaveDynamicFields = "0";
      Profile = "IntroCreditTextProfile";
      HorizSizing = "right";
      VertSizing = "top";
      Position = "30 315";
      Extent = "119 62";
      MinExtent = "8 2";
      canSave = "1";
      Visible = "1";
      hovertime = "1000";
      text = "";
      maxLength = "255";
[b]      fadeCtrl = true;
      fadeinTime = 2000;
      fadeoutTime = 2000;[/b]
   };

To initiate the fading in or out, just call the script function beginFadein or beginFadeout, as below for the GuiTextCtrl defined above:

[b]   IntroCredit.beginFadein();[/b]

That's all there is to it...let me know if there are any issues with the code.

Note: If you want to use the fade functions (by setting the control's fadeCtrl variable to true), the control assumes it starts from a fully transparent starting point. If you want the control to start already visible (fully faded in), then set the variables startFadein and fadeinDone to true. Then use the fadeout/fadein functions as usual.

#1
10/08/2007 (11:02 am)
Nice! Thanks for sharing! :-)
#2
10/08/2007 (1:46 pm)
I did recently note that the GuiTextCtrl::beginFadein() and GuiTextCtrl::beginFadeout() routines were defined to return bool's. XCode on the Mac ignores the fact that there is no return true; statement at the end of the function, but VS2005 on the PC doesn't like it.

So you could either add those return true; statements to each, or just make them void's.
#3
10/08/2007 (5:02 pm)
Excelent work! Thanks :)
#4
10/17/2007 (7:03 am)
I think it would be more useful to just have functions like text.fadeIn(500); text.fadeOut(500); So every textCtrl could be fade in/out easilly by script.
#5
10/17/2007 (7:40 am)
@Diego: that's a good idea, although the way it's currently set up, you can do it in script -- it just takes one extra line. All you would need to do (using the example GuiTextCtrl above) is to do:

IntroCredit.fadeinTime = 500;
IntroCredit.beginFadein();

The way I was approaching it, I didn't have many reasons to change a control's fadeinTime or fadeoutTime after it is created, so I just set them at the time of creation. But that doesn't mean you can't change them in script afterward.
#6
10/17/2007 (8:21 am)
Yeah, the way you did is prety good, i just preffer the way that torque already do with the StaticShapes startFade(int fadeTime, int delayTime, bool fadeOut); with just one method you can do whatever you want, and dont need to have a lot of variables exposed.
#7
02/04/2008 (8:06 am)
Do you think it will need a big change to work in TGEA?
#8
02/04/2008 (8:20 am)
I don't have any experience or knowledge of TGEA, so I can't comment.
#9
08/18/2008 (5:37 pm)
has anyone implemented this into GuiMLTextCtrl?