void VVirtualThumbStick::Reset()
{
  m_fXValue = 0.0f;
  m_fYValue = 0.0f;
  m_iLastTouchPointIndex = -1;

  // (Re)position thumb stick
  const VRectanglef finalValidArea = m_spTouchArea->GetArea();

  // Outer ring mask
  const float fInitialX = finalValidArea.m_vMin.x + finalValidArea.GetSizeX() * m_fRelativeInitialX;
  const float fInitialY = finalValidArea.m_vMin.y + finalValidArea.GetSizeY() * m_fRelativeInitialY;

  m_spRingMask->GetTextureSize(m_iRingWidth, m_iRingHeight);
  m_spRingMask->SetPos(
    fInitialX - static_cast<float>(m_iRingWidth / 2), 
    fInitialY - static_cast<float>(m_iRingHeight / 2));  

  // Inner circle mask
  m_iCircleCenterX = static_cast<int>(fInitialX);
  m_iCircleCenterY = static_cast<int>(fInitialY);

  m_spCircleMask->GetTextureSize(m_iCircleWidth, m_iCircleHeight);
  m_spCircleMask->SetPos(
    fInitialX - static_cast<float>(m_iCircleWidth / 2), 
    fInitialY - static_cast<float>(m_iCircleHeight / 2));
}
void VTextState::Paint(VGraphicsInfo *pGraphics, VWindowBase *pParentWnd, VColorRef iColor)
{
  const char *szText = GetText();
  if (!m_spFont || !szText || !szText[0])
    return;

  VRectanglef vParentRect = pParentWnd->GetClientRect(); // clipping box of parent control

  if(!m_bCachedLinesValid)
  {
    m_lines.Reset();
    m_lineOffsets.Reset();

    float fLineHeight = m_spFont->GetFontHeight() * m_fRelativeFontHeight * m_fFontScaling;

    if(!m_bTextWrap)
    {
      const char* szCurrentLine = szText;
      for (const char* szPtr = szCurrentLine; *szPtr != '\0'; ++szPtr)
      {
        if (*szPtr == '\n')
        {
          VStaticString<512> line;
          line.Set(szCurrentLine, static_cast<int>(szPtr - szCurrentLine));

          m_lines.Add(line.AsChar());
          szCurrentLine = szPtr + 1;
        }
      }

      // Add the last line
      if(*szCurrentLine)
      {
        m_lines.Add(szCurrentLine);
      }
    }
    else
    {
      float fMaxLineWidth = vParentRect.GetSizeX() / m_fFontScaling;

      // Wrap text into individual lines
      {
        const char *szCurrentLine = szText;
        while (*szCurrentLine)
        {
          // byte offsets
          int iByteOffsetAtWrapPosition;
          int iByteOffsetAfterWrapPosition;

          // search for next newline
          const char *pNextNewLine = strchr(szCurrentLine, '\n');

          // compute automatic wrap character index
          int iCharCount = m_spFont->GetCharacterIndexAtPos(szCurrentLine, fMaxLineWidth, -1, false);
          int iWrapOffset = VString::GetUTF8CharacterOffset(szCurrentLine, iCharCount);

          if (pNextNewLine != NULL && (pNextNewLine - szCurrentLine) <= iWrapOffset)
          {
            // newline occurs before automatic text wrap
            iByteOffsetAtWrapPosition = static_cast<int>(pNextNewLine - szCurrentLine);
            iByteOffsetAfterWrapPosition = iByteOffsetAtWrapPosition + 1;
          }
          else if(strlen(szCurrentLine) <= iWrapOffset)
          {
            // End of text occurs before automatic text wrap
            iByteOffsetAtWrapPosition = static_cast<int>(strlen(szCurrentLine));
            iByteOffsetAfterWrapPosition = iByteOffsetAtWrapPosition;
          }
          else
          {
            // automatic text wrap
            iByteOffsetAtWrapPosition = iWrapOffset;
            if (iByteOffsetAtWrapPosition > 0)
            {
              // Go backwards and try to find white space
              while (iByteOffsetAtWrapPosition > 0 && !IsWhiteSpace(szCurrentLine[iByteOffsetAtWrapPosition]))
              {
                iByteOffsetAtWrapPosition--;
              }

              // no whitespace found? then wrap inside word
              if (iByteOffsetAtWrapPosition == 0)
              {
                iByteOffsetAtWrapPosition = iWrapOffset;
              }
              else
              {
                // Find end of next word
                int iEndOfWord = iByteOffsetAtWrapPosition + 1;
                while(szCurrentLine[iEndOfWord] && !IsWhiteSpace(szCurrentLine[iEndOfWord]))
                {
                  iEndOfWord++;
                }

                // If the word does not fit into a line by itself, it will be wrapped anyway, so wrap it early to avoid ragged looking line endings
                VRectanglef nextWordSize;
                m_spFont->GetTextDimension(szCurrentLine + iByteOffsetAtWrapPosition, nextWordSize, iEndOfWord - iByteOffsetAtWrapPosition);
                if(nextWordSize.GetSizeX() > fMaxLineWidth)
                {
                  iByteOffsetAtWrapPosition = iWrapOffset;
                }
              }
            }
            else
            {
              // First character is already wider than the line
              iByteOffsetAtWrapPosition = VString::GetUTF8CharacterOffset(szCurrentLine, 1);
            }
            iByteOffsetAfterWrapPosition = iByteOffsetAtWrapPosition;
          }

          // put together line
          VStaticString<512> line;
          line.Set(szCurrentLine, iByteOffsetAtWrapPosition);

          m_lines.Add(line.AsChar());

          szCurrentLine = &szCurrentLine[iByteOffsetAfterWrapPosition];
          while(*szCurrentLine == ' ')
            szCurrentLine++;
        }
      }
    }

    // Compute offset for each line
    for(int iLineIdx = 0; iLineIdx < m_lines.GetLength(); iLineIdx++)
    {
      hkvVec2 offset = m_vOffset;

      offset.x += m_spFont->GetTextPositionOfs(m_lines[iLineIdx], vParentRect.GetSize(), m_hAlign, m_vAlign, m_fFontScaling).x;
      offset.y += iLineIdx * fLineHeight;

      if (m_vAlign == VisFont_cl::ALIGN_CENTER)
      {
        offset.y += (vParentRect.GetSizeY() - fLineHeight * m_lines.GetSize()) * 0.5f;
      }
      else if (m_vAlign == VisFont_cl::ALIGN_BOTTOM)
      {
        offset.y += (vParentRect.GetSizeY() - fLineHeight * m_lines.GetSize());
      }

      m_lineOffsets.Add(offset);
    }

    m_bCachedLinesValid = true;
  }

  // Render lines
  if (pGraphics)
  {
    VSimpleRenderState_t state = VGUIManager::DefaultGUIRenderState();
    if (m_fFontScaling!=1.0f)
      state.SetFlag(RENDERSTATEFLAG_FILTERING);

    for(int iLineIdx = 0; iLineIdx < m_lines.GetLength(); iLineIdx++)
    {
      m_spFont->PrintText(&pGraphics->Renderer, vParentRect.m_vMin + m_lineOffsets[iLineIdx], m_lines[iLineIdx], iColor, state, 
        m_fFontScaling, m_pCustomBBox ? m_pCustomBBox : &vParentRect);
    }
  }
}