/********************************************************************

  Module: TiUIControl

  Author: Carlo Ordonez

  Description:

    Widget drawing services

  Copyright 2005 Sony Online Entertainment.  All rights reserved.

*********************************************************************/

#include "TiUIControl.h"
#include "TiUI.h"
#include "TiUILayout.h"
#include "SyStr.h"
#include "SyRaster.h"
#include "TiSprite.h"

//***********************************************************************
// Constructor/Destructor
//***********************************************************************

TiUIControl::TiUIControl()
{
  mFade     = 0.0f;
  mFadeDir  = 1;
  mStyle    = kDefault;
  mTime     = 0;

  mpDefaultLayout = SyNew TiUILayout();
  mpDefaultLayout->Init( "menu_dialog.uiml" );

  mpChildLayout = SyNew TiUILayout();
  mpChildLayout->Init( "menu_dialog_child.uiml" );
}

TiUIControl::~TiUIControl()
{
  delete mpDefaultLayout;
  delete mpChildLayout;
}

//***********************************************************************
// Init
//***********************************************************************

int32 TiUIControl::Init( int32 Width, int32 NumRows, FrameStyle Style )
{
  return( Init( TiUI::Instance()->GetCenter(), Width, NumRows, Style ) );
}

//***********************************************************************
// Init
//***********************************************************************

int32 TiUIControl::Init( const SyVect2I& Center, int32 Width, int32 NumRows, FrameStyle Style )
{
  int y = TiUI::Instance()->GetFontHeight();

  mRows = NumRows;
  mCenter = Center;
  mClientSize.X = Width;
  mClientSize.Y = y * NumRows;
  mStyle = Style;

  InitColors();
  return( 0 );
}

//***********************************************************************
// Init
//***********************************************************************

int32 TiUIControl::Init( const SyVect2I& Center, const SyVect2I& ClientSize, FrameStyle Style )
{
  int y = TiUI::Instance()->GetFontHeight();

  mRows = ClientSize.Y / y;
  mCenter = Center;
  mClientSize = ClientSize;
  mStyle = Style;

  InitColors();
  return( 0 );
}

//***********************************************************************
// InitColors
//***********************************************************************

void TiUIControl::InitColors()
{
  switch( mStyle )
  {
  case kBlack:
    mTextColors[ kNormal ] = SyColor32F(.9f,.9f,.9f,1);
    mTextColors[ kSelected ] = SyColor32F(.6f,.6f,.6f,1);
    mTextColors[ kDisabled ] = SyColor32F(.4f,.4f,.4f,1);
    break;

  case kChild:
  case kDefault:
  default:
    mTextColors[ kNormal ] = SyColor32F(.9f,.9f,.9f,1);
    mTextColors[ kSelected ] = SyColor32F(.6f,.6f,.6f,1);
    mTextColors[ kDisabled ] = SyColor32F(.4f,.4f,.4f,1);
    break;
  }
}

//***********************************************************************
// Draw
//***********************************************************************

void TiUIControl::Draw()
{
  SyVect2I offset, size;
  const SyVect2I clientUL = GetClientUL();
  const SyVect2I clientLR = GetClientLR();
  const SyVect2I clientUR( clientLR.X, clientUL.Y );
  const SyVect2I clientLL( clientUL.X, clientLR.Y );

  TiUILayoutItem info;

  switch( mStyle )
  {
  case kNone:
    break;

  case kBlack:
    {
      // background
      const char8* pTile = "black.tga";
      TiUI::Instance()->Blit( pTile, 0, GetClientUL(), SyVect2I(0,0), GetClientSize(), SYALPHABLENDMODE_AVG, 0.65f * GetOpacity() );
    }
    break;

  case kChild:
    {
      // test
      //int32 w, h;
      //TiUI::Instance()->GetDisplaySize( &w, &h );
      //TiUI::Instance()->FillRectangle( SyVect2I(0,0), SyVect2I(w,h), SyColor32F(1,1,1,.7f) );
      //TiUI::Instance()->FillRectangle( GetClientUL(), GetClientLR(), SyColor32F( 0, 1, 0, .5f ) );

      // corners
      mpChildLayout->GetLayoutInfo( "cornerTopLeft", &info );
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientUL + info.mOffset, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpChildLayout->GetLayoutInfo( "cornerTopRight", &info );
      TiUI::Instance()->Blit( info.mFilename.AsChar(), SYRASTER_BLITFLAG_FLIP_HORIZONTAL, clientUR + info.mOffset, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpChildLayout->GetLayoutInfo( "cornerBottomRight", &info );
      TiUI::Instance()->Blit( info.mFilename.AsChar(), SYRASTER_BLITFLAG_FLIP_BOTH, clientLR + info.mOffset, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpChildLayout->GetLayoutInfo( "cornerBottomLeft", &info );
      TiUI::Instance()->Blit( info.mFilename.AsChar(), SYRASTER_BLITFLAG_FLIP_VERTICAL, clientLL + info.mOffset, SYALPHABLENDMODE_AVG, GetOpacity() );

      // gradient (twice)
      mpChildLayout->GetLayoutInfo( "interior", &info );
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );

      SyVect2I screenSize = GetClientSize();
      screenSize.Y /= 2;
      SyVect2I pt = clientUL + info.mOffset;

      screenSize.X -= 1;

      TiUI::Instance()->StretchBlit( info.mFilename.AsChar(), 0, pt, screenSize, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );
      pt.Y += screenSize.Y;
      TiUI::Instance()->StretchBlit( info.mFilename.AsChar(), SYRASTER_BLITFLAG_FLIP_VERTICAL, pt, screenSize, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );

      // vertical borders
      int32 barHeight = GetClientSize().Y;
      mpChildLayout->GetLayoutInfo( "borderLeft", &info );
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      screenSize(size.X,barHeight>>1);
      pt = clientUL + info.mOffset;

      TiUI::Instance()->StretchBlit( info.mFilename.AsChar(), 0, pt, screenSize, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );
      pt.Y += screenSize.Y;
      TiUI::Instance()->StretchBlit( info.mFilename.AsChar(), SYRASTER_BLITFLAG_FLIP_VERTICAL, pt, screenSize, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpChildLayout->GetLayoutInfo( "borderRight", &info );
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      pt = clientUR + info.mOffset;

      TiUI::Instance()->StretchBlit( info.mFilename.AsChar(), SYRASTER_BLITFLAG_FLIP_HORIZONTAL, pt, screenSize, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );
      pt.Y += screenSize.Y;
      TiUI::Instance()->StretchBlit( info.mFilename.AsChar(), SYRASTER_BLITFLAG_FLIP_BOTH, pt, screenSize, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );

      // horizontal borders
      int32 barWidth = GetClientSize().X - 1;

      mpChildLayout->GetLayoutInfo( "borderTop", &info );
      pt = clientUL + info.mOffset;
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      size.X = barWidth;
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, pt, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpChildLayout->GetLayoutInfo( "borderBottom", &info );
      pt = clientLL + info.mOffset;
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      size.X = barWidth;
      TiUI::Instance()->Blit( info.mFilename.AsChar(), SYRASTER_BLITFLAG_FLIP_VERTICAL, pt, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );
    }
    break;

  case kDefault:
  default:
    {
      // corners
      mpDefaultLayout->GetLayoutInfo( "cornerTopLeft", &info );
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientUL + info.mOffset, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpDefaultLayout->GetLayoutInfo( "cornerTopRight", &info );
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientUR + info.mOffset, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpDefaultLayout->GetLayoutInfo( "cornerBottomRight", &info );
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientLR + info.mOffset, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpDefaultLayout->GetLayoutInfo( "cornerBottomLeft", &info );
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientLL + info.mOffset, SYALPHABLENDMODE_AVG, GetOpacity() );

      // gradient
      mpDefaultLayout->GetLayoutInfo( "interior", &info );
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      SyVect2I surfaceSize = size;
      size = SyVect2I( GetClientSize().X - 2, GetClientSize().Y - 35 );
      TiUI::Instance()->StretchBlit( info.mFilename.AsChar(), 0, clientUL + info.mOffset, size, offset, surfaceSize, SYALPHABLENDMODE_AVG, GetOpacity() );

      // horizontal borders
      int32 barWidth = GetClientSize().X - 43;

      mpDefaultLayout->GetLayoutInfo( "borderTop", &info );
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      size.X = barWidth;
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientUL + info.mOffset, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpDefaultLayout->GetLayoutInfo( "borderBottom", &info );
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      size.X = barWidth;
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientLL + info.mOffset, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );

      // vertical borders
      int32 barHeight = GetClientSize().Y - 52;

      mpDefaultLayout->GetLayoutInfo( "borderLeft", &info );
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      size.Y = barHeight;
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientUL + info.mOffset, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );

      mpDefaultLayout->GetLayoutInfo( "borderRight", &info );
      TiUI::Instance()->GetBitmapInfo( info.mFilename.AsChar(), offset, size );
      size.Y= barHeight;
      TiUI::Instance()->Blit( info.mFilename.AsChar(), 0, clientUR + info.mOffset, offset, size, SYALPHABLENDMODE_AVG, GetOpacity() );
    }
    break;
  }
  UpdateFade();
}

void TiUIControl::UpdateFade()
{
  if( mFadeDir > 0 )
  {
    mFade = SY_MIN( 1.f, mFade + .15f );
  }
  else
  {
    mFade = SY_MAX( 0.f, mFade - .2f );
  }
}

//***********************************************************************
// DrawTextBox
//***********************************************************************

int32 TiUIControl::DrawTextBox( const SyString& Text, int32 X, int32 FirstRow, int32 Width, int32 RowCount, const SyColor32F& Color, uint32 Flags )
{
  return( DrawTextBox( Text.AsChar(), X, FirstRow, Width, RowCount, Color, Flags ) );
}

int32 TiUIControl::DrawTextBox( const SyString& Text, int32 X, int32 FirstRow, int32 Width, int32 RowCount, TextColor Color, uint32 Flags )
{
  return( DrawTextBox( Text.AsChar(), X, FirstRow, Width, RowCount, mTextColors[ Color ], Flags ) );
}

int32 TiUIControl::DrawTextBox( const char8* pText, int32 X, int32 FirstRow, int32 Width, int32 RowCount, TextColor Color, uint32 Flags )
{
  return( DrawTextBox( pText, X, FirstRow, Width, RowCount, mTextColors[ Color ], Flags ) );
}

int32 TiUIControl::DrawTextBox( const char8* pText, int32 X, int32 FirstRow, int32 Width, int32 RowCount, const SyColor32F& Color, uint32 Flags )
{
  // note: Flags == 0xffffffff signifies a super-duper-secret mode, in which we just count lines of text & return the result!

  int y_sp = TiUI::Instance()->GetFontHeight();

  // compute inner text box size (tight fit to actual text)

  SyVect2I text_pos( GetClientUL().X + X, GetRowCenter( FirstRow ).Y );
  SyVect2I text_dim( Width, RowCount * y_sp );

  if( Flags != 0xffffffff && ( Flags & TI_TF_FRAME ) )
  {
    DrawFrame( X, FirstRow, Width, RowCount, Flags );
  }

  // write out formatted text

  int32         line_width  = 0;
  int32         count       = 0;
  int32         space_loc   = 0;
  int32         break_loc   = 0;
  int32         row_count   = 0;
  const char8*  line_base   = pText;
  char8         row_buf[256];
  int32         line_offset = ((Flags == 0xffffffff) ? 0 : (Flags>>24));
  int32         line_index  = 0;

  // word wrapping logic
  while( pText[count] != 0 )
  {
    if( pText[count] == '\n' )
    {
      // output current row
      int32 len = int(&pText[count]-line_base);

      // in case of newlines w/no text
      if( len < 0 ) { len = 0; }

      if( (Flags != 0xffffffff) )
      {
        SyStr::Strncpy( row_buf, line_base, len+1 );
      }
      row_buf[ len ] = 0;

      if( Flags != 0xffffffff )
      {
        if( (Flags & TI_TF_ELLIPSIS) && (row_count+1 == RowCount) )
        {
          if( SyStr::strlen( &pText[count+1] ) ) // still remaining text, so append ellipsis
          {
            AppendEllipsis( row_buf, text_dim.X );
          }
        }
      }

      // OUTPUT
      if( line_index >= line_offset )
      {
        if( Flags != 0xffffffff )
        {
          if( Flags & TI_TF_HALIGN_CENTER )
          {
            DrawTextInternal( row_buf, text_pos.X + (text_dim.X>>1) - (GetTextWidth( row_buf )>>1), GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
          }
          else if( Flags & TI_TF_HALIGN_RIGHT )
          {
            DrawTextInternal( row_buf, text_pos.X + text_dim.X - GetTextWidth( row_buf ), GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
          } 
          else
          {
            DrawTextInternal( row_buf, text_pos.X, GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
          }
        }

        row_count++;
      }

      line_index++;

      // advance past '\n' & update line base

      count++;
      line_base = &pText[count];
      space_loc = 0;
      break_loc = 0;

      if( row_count >= RowCount )
      {
        break;
      }

      continue;
    }

    // compute width of current line in pixels

    int32 len = int(&pText[count]-line_base);

    // in case of newlines w/no text
    if( len < 0 ) { len = 0; }

    if( (Flags != 0xffffffff) )
    {
      SyStr::Strncpy( row_buf, line_base, len+1 );
    }
    row_buf[ len ] = 0;
    int32 last_line_width = line_width;
    line_width = GetTextWidth( row_buf );

    // need to force a line break?

    if( line_width >= (text_dim.X-6) ) // small cushion
    {
      // output text
      if( space_loc && ( !(Flags & TI_TF_HARD_BREAK) || Flags == 0xffffffff ) )
      {
        len = int(&pText[space_loc+1]-line_base);

        // in case of newlines w/no text
        if( len < 0 ) { len = 0; }

        if( (Flags != 0xffffffff) )
        {
          SyStr::Strncpy( row_buf, line_base, len+1 );
        }
        row_buf[ len ] = 0;

        count -= (count - space_loc - 1);
        line_base = &pText[count];

      }
      else
      {
        // hard break
        len = int(&pText[break_loc]-line_base);

        // in case of newlines w/no text
        if( len < 0 ) { len = 0; }

        if( (Flags != 0xffffffff) )
        {
          SyStr::Strncpy( row_buf, line_base, len+1 );
        }
        row_buf[ len ] = 0;

        count -= (count - break_loc);
        line_base = &pText[break_loc];
      }

      if( Flags != 0xffffffff )
      {
        if( (Flags & TI_TF_ELLIPSIS) && (row_count+1 == RowCount) )
        {
          if( SyStr::strlen( line_base ) ) // still remaining text, so append ellipsis
          {
            AppendEllipsis( row_buf, text_dim.X );
          }
        }
      }

      // OUTPUT
      if( line_index >= line_offset )
      {
        if( Flags != 0xffffffff )
        {
          if( Flags & TI_TF_HALIGN_CENTER )
          {
            DrawTextInternal( row_buf, text_pos.X + (text_dim.X>>1) - (GetTextWidth( row_buf )>>1), GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
          }
          else if( Flags & TI_TF_HALIGN_RIGHT )
          {
            DrawTextInternal( row_buf, text_pos.X + text_dim.X - GetTextWidth( row_buf ), GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
          }
          else
          {
            DrawTextInternal( row_buf, text_pos.X, GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
          }
        }

        row_count++;
      }

      line_index++;

      if( row_count >= RowCount )
      {
        break;
      }

      space_loc = 0;
      break_loc = 0;

      continue;
    } 
    else
    {
      if( last_line_width != line_width )
      {
        break_loc = count;
      }
    }

    // keep track of last 'space'
    if( pText[count] == ' ' )
    {
      space_loc = count;
    }

    // bump to next character
    count++;
  }

  // Do we have left-overs?
  if( &pText[count]-line_base )
  {
    int32 len = int(&pText[count]-line_base);

    // in case of newlines w/no text
    if( len < 0 ) { len = 0; }

    if( (Flags != 0xffffffff) )
    {
      SyStr::Strncpy( row_buf, line_base, len+1 );
    }
    row_buf[ len ] = 0;

    // OUTPUT
    if( line_index >= line_offset )
    {
      if( Flags != 0xffffffff )
      {
        if( Flags & TI_TF_HALIGN_CENTER )
        {
          DrawTextInternal( row_buf, text_pos.X + (text_dim.X>>1) - (GetTextWidth( row_buf )>>1), GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
        }
        else if( Flags & TI_TF_HALIGN_RIGHT )
        {
          DrawTextInternal( row_buf, text_pos.X + text_dim.X - GetTextWidth( row_buf ), GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
        }
        else
        {
          DrawTextInternal( row_buf, text_pos.X, GetRowCenter( FirstRow + row_count ).Y, Color, Flags );
        }
      }

      row_count++;
    }
  }

  return( row_count );
}

//***********************************************************************
// GetClientUL
//***********************************************************************

const SyVect2I TiUIControl::GetClientUL() const 
{
  return( SyVect2I( mCenter.X-(mClientSize.X>>1), mCenter.Y-(mClientSize.Y>>1) ) );
}

//***********************************************************************
// GetClientLR
//***********************************************************************

const SyVect2I TiUIControl::GetClientLR() const
{
  return( SyVect2I( mCenter.X+(mClientSize.X>>1), mCenter.Y+(mClientSize.Y>>1) ) );
}

//***********************************************************************
// IsOpening
//***********************************************************************

bool TiUIControl::IsOpening() const
{
  if( mFade < 1.f && mFadeDir == 1 )
  {
    return( true );
  }
  return( false );
}

//***********************************************************************
// IsOpen
//***********************************************************************

bool TiUIControl::IsOpen() const
{
  if( mFade == 1.f )
  {
    return( true );
  }
  return( false );
}

//***********************************************************************
// Open
//***********************************************************************

void TiUIControl::Open( bool Instant /*= false*/ )
{
  if( Instant )
  {
    mFade = 1.0f;
  }

  mFadeDir = 1;
}

//***********************************************************************
// IsClosing
//***********************************************************************

bool TiUIControl::IsClosing() const
{
  if( mFade > 0.f && mFadeDir == -1 )
  {
    return( true );
  }
  return( false );
}

//***********************************************************************
// IsClosed
//***********************************************************************

bool TiUIControl::IsClosed() const
{
  if( mFade == 0.f )
  {
    return( true );
  }
  return( false );
}

//***********************************************************************
// Close
//***********************************************************************

void TiUIControl::Close( bool Instant /*= false*/ )
{
  if( Instant )
  {
    mFade = 0.0f;
  }
  mFadeDir = -1;
}

//***********************************************************************
// RowCenter
//***********************************************************************

const SyVect2I TiUIControl::GetRowCenter( int32 Row )
{
  SyVect2I pos;

  int y_sp = TiUI::Instance()->GetFontHeight();

  pos.X = mCenter.X - 8;
  pos.Y = mCenter.Y - (mClientSize.Y>>1) + 10 + y_sp * Row;

  return( pos );
}

//***********************************************************************
// GetTextWidth
//***********************************************************************

int32 TiUIControl::GetTextWidth( const SyString& Text )
{
  return( GetTextWidth( Text.AsChar() ) );
}

int32 TiUIControl::GetTextWidth( const char8* pText )
{
  char8 buf[1024];
  if( SyStr::strlen( pText ) >= 1024 )
  {
    return( 0 );
  }

  int32 extra = 0; // extra non-text width

  // parse out escape sequences, noting the ones that have physical width

  const char8*  in = pText;
  char8*        out = buf;

  while( *in )
  {
    if( in[0] == '|' ) // our escape character
    {
      if( in[1] == 0 )
      {
        break;
      }
      switch( in[1] )
      {
      case 't': // triangle
      case 's': // square
      case 'c': // circle
      case 'x': // x
        extra += 41;
        break;
      case 'a': // start
        extra += 35;
        break;
      case 'A': // select
        extra += 39;
        break;
      case 'l': // L1
      case 'L': // L2
      case 'r': // R1
      case 'R': // R2
        extra += 51;
        break;
      case 'z': // up
      case 'Z': // down
        extra += 27;
        break;
      case 'v': // left
      case 'V': // right
        extra += 29;
        break;

      case '|':
        *out++ = '|';
        break;

      default: // non printable
        break;
      }

      in++;
    }
    else
    {
      *out++ = in[0];
    }

    in++;
  }

  *out = 0;

  return( TiUI::Instance()->GetStringWidth( buf ) + extra );
}

//***********************************************************************
// DrawTextInternal
//***********************************************************************

void TiUIControl::DrawTextInternal( const char8* Text, int32 X, int32 Y, const SyColor32F& Color, uint32 Flags )
{
  char8 buf[1024];

  int32 x          = X;
  SyColor32F color = Color;

  const char8*  in  = Text;
  char8*        out = buf;

  while( *in )
  {
    if( in[0] == '|' ) // our escape character
    {
      if( in[1] == 0 )
      {
        break;
      }

      // flush current text (if any)
      *out = 0;

      if( out-buf )
      {
        TiUI::Instance()->DrawCenteredString( buf, SyVect2I(x,Y), SyColor32F( color.R, color.G, color.B, color.A * GetOpacity() ), SYRASTER_CENTERFLAG_VCENTER, (Flags&TI_TF_SHADOW) ? true : false );
        x += TiUI::Instance()->GetStringWidth( buf );
      }

      out = buf;

      switch( in[1] )
      {
      case 't': // triangle
        TiUI::Instance()->Blit( "hud_controller_triangle.tga", 0, SyVect2I( x, Y-18 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 41;
        break;

      case 's': // square
        TiUI::Instance()->Blit( "hud_controller_square.tga", 0, SyVect2I( x, Y-18 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 41;
        break;

      case 'c': // circle
        TiUI::Instance()->Blit( "hud_controller_circle.tga", 0, SyVect2I( x, Y-18 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 41;
        break;

      case 'x': // x
        TiUI::Instance()->Blit( "hud_controller_cross.tga", 0, SyVect2I( x, Y-18 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 41;
        break;

      case 'a': // start
        TiUI::Instance()->Blit( "hud_controller_start.tga", 0, SyVect2I( x, Y-15 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 35;
        break;

      case 'A': // select
        TiUI::Instance()->Blit( "hud_controller_select.tga", 0, SyVect2I( x, Y-15 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 39;
        break;

      case 'l': // L1
        TiUI::Instance()->Blit( "hud_controller_L1.tga", 0, SyVect2I( x, Y-15 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 51;
        break;

      case 'L': // L2
        TiUI::Instance()->Blit( "hud_controller_L2.tga", 0, SyVect2I( x, Y-15 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 51;
        break;

      case 'r': // R1
        TiUI::Instance()->Blit( "hud_controller_R1.tga", 0, SyVect2I( x, Y-15 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 51;
        break;

      case 'R': // R2
        TiUI::Instance()->Blit( "hud_controller_R2.tga", 0, SyVect2I( x, Y-15 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 51;
        break;

      case 'z': // up
        TiUI::Instance()->Blit( "hud_controller_arrow.tga", SYRASTER_BLITFLAG_FLIP_VERTICAL, SyVect2I( x, Y-12 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 27;
        break;

      case 'Z': // down
        TiUI::Instance()->Blit( "hud_controller_arrow.tga", 0, SyVect2I( x, Y-12 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 27;
        break;

      case 'v': // left
        TiUI::Instance()->Blit( "hud_controller_arrow_02.tga", SYRASTER_BLITFLAG_FLIP_HORIZONTAL, SyVect2I( x, Y-12 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 29;
        break;

      case 'V': // right
        TiUI::Instance()->Blit( "hud_controller_arrow_02.tga", 0, SyVect2I( x, Y-12 ), SYALPHABLENDMODE_AVG, GetOpacity() );
        x += 29;
        break;

      case '|':
        *out++ = '|';
        break;

      default: // non printable
        break;
      }

      in++;
    }
    else if( in[0] == '\r' )
    {
      *out++ = ' ';
    }
    else
    {
      *out++ = in[0];
    }

    in++;
  }

  // flush remaining text (if any)
  *out = 0;

  if( out-buf )
  {
    TiUI::Instance()->DrawCenteredString( buf, SyVect2I(x,Y), SyColor32F( color.R, color.G, color.B, color.A * GetOpacity() ), SYRASTER_CENTERFLAG_VCENTER, (Flags&TI_TF_SHADOW) ? true : false );
  }
}

//***********************************************************************
// AppendEllipsis
//***********************************************************************

void TiUIControl::AppendEllipsis( char8* pText, int32 MaxWidth )
{
  int32 len            = SyStr::strlen( pText );
  int32 ellipsis_width = TiUI::Instance()->GetStringWidth( "..." );

  if( ellipsis_width >= MaxWidth )
  {
    return;
  }

  // output ellipsis
  while( GetTextWidth( pText ) + ellipsis_width >= MaxWidth )
  {
    len--;
    pText[len] = 0;
  }

  strcat( pText, "..." );
}

//***********************************************************************
// DrawFrame
//***********************************************************************

void TiUIControl::DrawFrame( int32 X, int32 FirstRow, int32 Width, int32 RowCount, uint32 Flags )
{
  // TODO: get art
  int y_sp = TiUI::Instance()->GetFontHeight();

  SyVect2I text_pos( GetClientUL().X + X, GetRowCenter( FirstRow ).Y );
  SyVect2I text_dim( Width, RowCount * y_sp );

  SyVect2I frame_pos( text_pos.X - 4, text_pos.Y - 10 );
  SyVect2I frame_dim( text_dim.X + 8, text_dim.Y );

  TiUI::Instance()->Blit( "black.tga", 0, frame_pos, SyVect2I(0,0), frame_dim, SYALPHABLENDMODE_AVG, .25f * GetOpacity() );
}

//***********************************************************************
// DrawProgressBar
//***********************************************************************

void TiUIControl::DrawProgressBar( int32 X, int32 Row, int32 Width, float32 Progress )
{
  // TODO: get art
  int y_sp = TiUI::Instance()->GetFontHeight();

  SyVect2I text_pos( GetClientUL().X + X, GetRowCenter( Row ).Y - (y_sp>>1) + 7 );
  SyVect2I text_dim( Width, y_sp - 12 );

  text_dim.X = int32( float(text_dim.X) * Progress );

  SyVect2I rect[2];

  rect[0] = text_pos;
  rect[1] = text_pos;
  rect[1] += text_dim;

  SyColor32F color( .83f, .65f, 0, GetOpacity() );

  TiUI::Instance()->FillRectangle( rect[0], rect[1], color );
}

//***********************************************************************
// DrawVScroll
//***********************************************************************

void TiUIControl::DrawVScroll( int32 X, int32 Row, int32 Width, int32 RowCount, float32 ScrollPos )
{
  const char8* pArrow   = "menu_dialog_scrollbar_arrow.tga";
  const char8* pThumb   = "menu_dialog_scrollbar_button.tga";
  const char8* pEnd     = "menu_dialog_scrollbar_end.tga";
  const char8* pTile    = "menu_dialog_scrollbar.tga";

  SyVect2I offset, size;

  int32 halfRow = TiUI::Instance()->GetFontHeight() / 2;
  int32 x = GetClientUL().X + X;
  int32 topY = 0;
  
  topY = GetRowCenter( Row ).Y - halfRow + 3;
  SyVect2I tile( x, topY + 12 );
  int32 tileH = TiUI::Instance()->GetFontHeight() * RowCount - 24;

  // top & center bar
  TiUI::Instance()->GetBitmapInfo( pTile, offset, size );
  TiUI::Instance()->Blit( pTile, 0, tile, offset, SyVect2I(size.X,tileH), SYALPHABLENDMODE_AVG, GetOpacity() );
  TiUI::Instance()->Blit( pEnd, 0, SyVect2I(x,topY), SYALPHABLENDMODE_AVG, GetOpacity() );
  
  // bottom
  int32 y = GetRowCenter( Row+RowCount-1 ).Y + halfRow - 8;
  TiUI::Instance()->Blit( pEnd, SYRASTER_BLITFLAG_FLIP_VERTICAL, SyVect2I(x,y), SYALPHABLENDMODE_AVG, GetOpacity() );

  // thumb
  if( ScrollPos >= 0.f && ScrollPos <= 1.f )
  {
    y = tile.Y + int32(ScrollPos * (float32)(tileH-8));
    TiUI::Instance()->Blit( pThumb, 0, SyVect2I(x,y), SYALPHABLENDMODE_AVG, GetOpacity() );
  }

  // arrows, if needed
  if( ScrollPos > 0.f )
  {
    SyVect2I v = GetRowCenter( Row-1 );
    TiUI::Instance()->Blit( pArrow, SYRASTER_BLITFLAG_FLIP_VERTICAL, v, SYALPHABLENDMODE_AVG, GetOpacity() );
  }
  if( ScrollPos < 1.f )
  {
    SyVect2I v = GetRowCenter( Row+RowCount );
    v.Y -= 5;
    TiUI::Instance()->Blit( pArrow, 0, v, SYALPHABLENDMODE_AVG, GetOpacity() );
  }
}

//***********************************************************************
// GetTickPulse
//***********************************************************************

float32 TiUIControl::GetTickPulse() const
{
  float32 step = 1.f - mFade;
  step = 1.f - step*step;

  float pulse = (TiUI::Instance()->GetTime() - mTime) * .01f;

  if( pulse > 2.f * SY_PI )
  {
    pulse -= 2.f * SY_PI;
  }

  return( step * (.5f + .5f*sinf( pulse + SY_PI*.5f )) );
}

//***********************************************************************
// ResetTickPulse
//***********************************************************************

void TiUIControl::ResetTickPulse()
{
  mTime = TiUI::Instance()->GetTime();
}

//***********************************************************************
// SetOpacity
//***********************************************************************

void TiUIControl::SetOpacity( float Opacity )
{
  mFade = SY_CLAMP( Opacity, 0.f, 1.f );
}

//***********************************************************************
// CountLines
//***********************************************************************

int32 TiUIControl::CountLines( const char8* pText, int32 Width )
{
  return( DrawTextBox( pText, 0, 0, Width, 256, kNormal, 0xffffffff ) );
}

//***********************************************************************
// ReloadLayout
//***********************************************************************

void TiUIControl::ReloadLayout()
{
  mpDefaultLayout->Reload();
  mpChildLayout->Reload();
}
