Browse Source

Improved and unified API for Text & Text3D: get number of Unicode characters, get width of row by index, get position of character by index, get size of character by index. Store positions & sizes in the same vector to reduce amount of dynamic memory allocations.

Lasse Öörni 12 years ago
parent
commit
8cc5ad7fd2

+ 4 - 3
Source/Engine/LuaScript/pkgs/UI/Text.pkg

@@ -41,9 +41,10 @@ class Text : public UIElement
     const Color& GetEffectColor() const;
     const Color& GetEffectColor() const;
     int GetRowHeight() const;
     int GetRowHeight() const;
     unsigned GetNumRows() const;
     unsigned GetNumRows() const;
-    const PODVector<int>& GetRowWidths() const;
-    const PODVector<IntVector2>& GetCharPositions();
-    const PODVector<IntVector2>& GetCharSizes();
+    unsigned GetNumChars() const;
+    int GetRowWidth(unsigned index) const;
+    IntVector2 GetCharPosition(unsigned index);
+    IntVector2 GetCharSize(unsigned index);
     
     
     void SetEffectDepthBias(float bias);
     void SetEffectDepthBias(float bias);
     float GetEffectDepthBias() const;
     float GetEffectDepthBias() const;

+ 4 - 1
Source/Engine/LuaScript/pkgs/UI/Text3D.pkg

@@ -42,7 +42,10 @@ class Text3D : public Drawable
     int GetWidth() const;
     int GetWidth() const;
     int GetRowHeight() const;
     int GetRowHeight() const;
     unsigned GetNumRows() const;
     unsigned GetNumRows() const;
-    const PODVector<int>& GetRowWidths() const;
+    unsigned GetNumChars() const;
+    int GetRowWidth(unsigned index) const;
+    IntVector2 GetCharPosition(unsigned index);
+    IntVector2 GetCharSize(unsigned index);
     const Color& GetColor(Corner corner) const;
     const Color& GetColor(Corner corner) const;
     float GetOpacity() const;
     float GetOpacity() const;
     bool GetFaceCamera() const;
     bool GetFaceCamera() const;

+ 8 - 0
Source/Engine/Script/UIAPI.cpp

@@ -350,6 +350,10 @@ static void RegisterText(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Text", "void set_effectColor(const Color&in)", asMETHOD(Text, SetEffectColor), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "void set_effectColor(const Color&in)", asMETHOD(Text, SetEffectColor), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "const Color& get_effectColor() const", asMETHOD(Text, GetEffectColor), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "const Color& get_effectColor() const", asMETHOD(Text, GetEffectColor), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "uint get_numRows() const", asMETHOD(Text, GetNumRows), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "uint get_numRows() const", asMETHOD(Text, GetNumRows), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "uint get_numChars() const", asMETHOD(Text, GetNumChars), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "int get_rowWidths(uint) const", asMETHOD(Text, GetRowWidth), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "IntVector2 get_charPositions(uint)", asMETHOD(Text, GetCharPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "IntVector2 get_charSizes(uint)", asMETHOD(Text, GetCharSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "int get_rowHeight() const", asMETHOD(Text, GetRowHeight), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "int get_rowHeight() const", asMETHOD(Text, GetRowHeight), asCALL_THISCALL);
 }
 }
 
 
@@ -391,6 +395,10 @@ static void RegisterText3D(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Text3D", "void set_faceCamera(bool)", asMETHOD(Text3D, SetFaceCamera), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "void set_faceCamera(bool)", asMETHOD(Text3D, SetFaceCamera), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "bool get_faceCamera() const", asMETHOD(Text3D, GetFaceCamera), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "bool get_faceCamera() const", asMETHOD(Text3D, GetFaceCamera), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "uint get_numRows() const", asMETHOD(Text3D, GetNumRows), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "uint get_numRows() const", asMETHOD(Text3D, GetNumRows), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text3D", "uint get_numChars() const", asMETHOD(Text3D, GetNumChars), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text3D", "int get_rowWidths(uint) const", asMETHOD(Text3D, GetRowWidth), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text3D", "IntVector2 get_charPositions(uint)", asMETHOD(Text3D, GetCharPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text3D", "IntVector2 get_charSizes(uint)", asMETHOD(Text3D, GetCharSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "int get_rowHeight() const", asMETHOD(Text3D, GetRowHeight), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "int get_rowHeight() const", asMETHOD(Text3D, GetRowHeight), asCALL_THISCALL);
 }
 }
 
 

+ 4 - 7
Source/Engine/UI/LineEdit.cpp

@@ -570,10 +570,7 @@ void LineEdit::UpdateText()
 
 
 void LineEdit::UpdateCursor()
 void LineEdit::UpdateCursor()
 {
 {
-    int x = 0;
-    const PODVector<IntVector2>& charPositions = text_->GetCharPositions();
-    if (charPositions.Size())
-        x = cursorPosition_ < charPositions.Size() ? charPositions[cursorPosition_].x_ : charPositions.Back().x_;
+    int x = text_->GetCharPosition(cursorPosition_).x_;
 
 
     text_->SetPosition(GetIndentWidth() + clipBorder_.left_, clipBorder_.top_);
     text_->SetPosition(GetIndentWidth() + clipBorder_.left_, clipBorder_.top_);
     cursor_->SetPosition(text_->GetPosition() + IntVector2(x, 0));
     cursor_->SetPosition(text_->GetPosition() + IntVector2(x, 0));
@@ -599,14 +596,14 @@ unsigned LineEdit::GetCharIndex(const IntVector2& position)
 {
 {
     IntVector2 screenPosition = ElementToScreen(position);
     IntVector2 screenPosition = ElementToScreen(position);
     IntVector2 textPosition = text_->ScreenToElement(screenPosition);
     IntVector2 textPosition = text_->ScreenToElement(screenPosition);
-    const PODVector<IntVector2>& charPositions = text_->GetCharPositions();
 
 
     if (textPosition.x_ < 0)
     if (textPosition.x_ < 0)
         return 0;
         return 0;
 
 
-    for (unsigned i = charPositions.Size() - 1; i < charPositions.Size(); --i)
+    int numChars = text_->GetNumChars();
+    for (int i = numChars; i >= 0; --i)
     {
     {
-        if (textPosition.x_ >= charPositions[i].x_)
+        if (textPosition.x_ >= text_->GetCharPosition(i).x_)
             return i;
             return i;
     }
     }
 
 

+ 131 - 121
Source/Engine/UI/Text.cpp

@@ -53,7 +53,7 @@ Text::Text(Context* context) :
     textAlignment_(HA_LEFT),
     textAlignment_(HA_LEFT),
     rowSpacing_(1.0f),
     rowSpacing_(1.0f),
     wordWrap_(false),
     wordWrap_(false),
-    charPositionsDirty_(true),
+    charLocationsDirty_(true),
     selectionStart_(0),
     selectionStart_(0),
     selectionLength_(0),
     selectionLength_(0),
     selectionColor_(Color::TRANSPARENT),
     selectionColor_(Color::TRANSPARENT),
@@ -108,6 +108,23 @@ void Text::ApplyAttributes()
 
 
 void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
 void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
 {
 {
+    FontFace* face = font_ ? font_->GetFace(fontSize_) : (FontFace*)0;
+    if (!face)
+    {
+        hovering_ = false;
+        return;
+    }
+    
+    // If face has changed or char locations are not valid anymore, update before rendering
+    if (charLocationsDirty_ || !fontFace_ || face != fontFace_)
+        UpdateCharLocations();
+    // If face uses mutable glyphs mechanism, reacquire glyphs before rendering to make sure they are in the texture
+    else if (face->HasMutableGlyphs())
+    {
+        for (unsigned i = 0; i < printText_.Size(); ++i)
+            face->GetGlyph(printText_[i]);
+    }
+    
     // Hovering and/or whole selection batch
     // Hovering and/or whole selection batch
     if ((hovering_ && hoverColor_.a_ > 0.0) || (selected_ && selectionColor_.a_ > 0.0f))
     if ((hovering_ && hoverColor_.a_ > 0.0) || (selected_ && selectionColor_.a_ > 0.0f))
     {
     {
@@ -120,29 +137,29 @@ void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData,
     }
     }
 
 
     // Partial selection batch
     // Partial selection batch
-    if (!selected_ && selectionLength_ && charSizes_.Size() >= selectionStart_ + selectionLength_ && selectionColor_.a_ > 0.0f)
+    if (!selected_ && selectionLength_ && charLocations_.Size() >= selectionStart_ + selectionLength_ && selectionColor_.a_ > 0.0f)
     {
     {
         UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
         UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
         batch.SetColor(selectionColor_);
         batch.SetColor(selectionColor_);
 
 
-        IntVector2 currentStart = charPositions_[selectionStart_];
+        IntVector2 currentStart = charLocations_[selectionStart_].position_;
         IntVector2 currentEnd = currentStart;
         IntVector2 currentEnd = currentStart;
         for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
         for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
         {
         {
             // Check if row changes, and start a new quad in that case
             // Check if row changes, and start a new quad in that case
-            if (charSizes_[i].x_ && charSizes_[i].y_)
+            if (charLocations_[i].size_ != IntVector2::ZERO)
             {
             {
-                if (charPositions_[i].y_ != currentStart.y_)
+                if (charLocations_[i].position_.y_ != currentStart.y_)
                 {
                 {
                     batch.AddQuad(currentStart.x_, currentStart.y_, currentEnd.x_ - currentStart.x_, currentEnd.y_ - currentStart.y_,
                     batch.AddQuad(currentStart.x_, currentStart.y_, currentEnd.x_ - currentStart.x_, currentEnd.y_ - currentStart.y_,
                         0, 0);
                         0, 0);
-                    currentStart = charPositions_[i];
-                    currentEnd = currentStart + charSizes_[i];
+                    currentStart = charLocations_[i].position_;
+                    currentEnd = currentStart + charLocations_[i].size_;
                 }
                 }
                 else
                 else
                 {
                 {
-                    currentEnd.x_ += charSizes_[i].x_;
-                    currentEnd.y_ = Max(currentStart.y_ + charSizes_[i].y_, currentEnd.y_);
+                    currentEnd.x_ += charLocations_[i].size_.x_;
+                    currentEnd.y_ = Max(currentStart.y_ + charLocations_[i].size_.y_, currentEnd.y_);
                 }
                 }
             }
             }
         }
         }
@@ -156,56 +173,39 @@ void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData,
     }
     }
 
 
     // Text batch
     // Text batch
-    if (font_)
+    const Vector<SharedPtr<Texture2D> >& textures = face->GetTextures();
+    for (unsigned n = 0; n < textures.Size() && n < pageGlyphLocations_.Size(); ++n)
     {
     {
-        FontFace* face = font_->GetFace(fontSize_);
-        if (!face)
-            return;
-        
-        // If face has changed or char positions are not valid anymore, update before rendering
-        if (charPositionsDirty_ || !fontFace_ || face != fontFace_)
-            UpdateCharPositions();
-        // If face uses mutable glyphs mechanism, reacquire glyphs before rendering to make sure they are in the texture
-        else if (face->HasMutableGlyphs())
-        {
-            for (unsigned i = 0; i < printText_.Size(); ++i)
-                face->GetGlyph(printText_[i]);
-        }
-        
-        const Vector<SharedPtr<Texture2D> >& textures = face->GetTextures();
-        for (unsigned n = 0; n < textures.Size() && n < pageGlyphLocations_.Size(); ++n)
-        {
-            // One batch per texture/page
-            UIBatch pageBatch(this, BLEND_ALPHA, currentScissor, textures[n], &vertexData);
+        // One batch per texture/page
+        UIBatch pageBatch(this, BLEND_ALPHA, currentScissor, textures[n], &vertexData);
 
 
-            const PODVector<GlyphLocation>& pageGlyphLocation = pageGlyphLocations_[n];
+        const PODVector<GlyphLocation>& pageGlyphLocation = pageGlyphLocations_[n];
 
 
-            switch (textEffect_)
-            {
-            case TE_NONE:
-                ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
-                break;
-                
-            case TE_SHADOW:
-                ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
-                break;
-                
-            case TE_STROKE:
-                ConstructBatch(pageBatch, pageGlyphLocation, -1, -1, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, 0, -1, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, 1, -1, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, -1, 0, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, 1, 0, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, -1, 1, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, 0, 1, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
-                ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
-                break;
-            }
-
-            UIBatch::AddOrMerge(pageBatch, batches);
+        switch (textEffect_)
+        {
+        case TE_NONE:
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
+            break;
+            
+        case TE_SHADOW:
+            ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
+            break;
+            
+        case TE_STROKE:
+            ConstructBatch(pageBatch, pageGlyphLocation, -1, -1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, -1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 1, -1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, -1, 0, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 1, 0, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, -1, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
+            break;
         }
         }
+
+        UIBatch::AddOrMerge(pageBatch, batches);
     }
     }
 
 
     // Reset hovering for next frame
     // Reset hovering for next frame
@@ -217,12 +217,12 @@ void Text::OnResize()
     if (wordWrap_)
     if (wordWrap_)
         UpdateText();
         UpdateText();
     else
     else
-        charPositionsDirty_ = true;
+        charLocationsDirty_ = true;
 }
 }
 
 
 void Text::OnIndentSet()
 void Text::OnIndentSet()
 {
 {
-    charPositionsDirty_ = true;
+    charLocationsDirty_ = true;
 }
 }
 
 
 bool Text::SetFont(const String& fontName, int size)
 bool Text::SetFont(const String& fontName, int size)
@@ -267,7 +267,7 @@ void Text::SetTextAlignment(HorizontalAlignment align)
     if (align != textAlignment_)
     if (align != textAlignment_)
     {
     {
         textAlignment_ = align;
         textAlignment_ = align;
-        charPositionsDirty_ = true;
+        charLocationsDirty_ = true;
     }
     }
 }
 }
 
 
@@ -327,18 +327,33 @@ void Text::SetEffectDepthBias(float bias)
     effectDepthBias_ = bias;
     effectDepthBias_ = bias;
 }
 }
 
 
-const PODVector<IntVector2>& Text::GetCharPositions()
+int Text::GetRowWidth(unsigned index) const
+{
+    return index < rowWidths_.Size() ? rowWidths_[index] : 0;
+}
+
+IntVector2 Text::GetCharPosition(unsigned index)
 {
 {
-    if (charPositionsDirty_)
-        UpdateCharPositions();
-    return charPositions_;
+    if (charLocationsDirty_)
+        UpdateCharLocations();
+    if (charLocations_.Empty())
+        return IntVector2::ZERO;
+    // For convenience, return the position of the text ending if index exceeded
+    if (index > charLocations_.Size() - 1)
+        index = charLocations_.Size() - 1;
+    return charLocations_[index].position_;
 }
 }
 
 
-const PODVector<IntVector2>& Text::GetCharSizes()
+IntVector2 Text::GetCharSize(unsigned index)
 {
 {
-    if (charPositionsDirty_)
-        UpdateCharPositions();
-    return charSizes_;
+    if (charLocationsDirty_)
+        UpdateCharLocations();
+    if (charLocations_.Size() < 2)
+        return IntVector2::ZERO;
+    // For convenience, return the size of the last char if index exceeded (last size entry is zero)
+    if (index > charLocations_.Size() - 2)
+        index = charLocations_.Size() - 2;
+    return charLocations_[index].size_;
 }
 }
 
 
 void Text::SetFontAttr(ResourceRef value)
 void Text::SetFontAttr(ResourceRef value)
@@ -539,7 +554,7 @@ void Text::UpdateText()
         }
         }
         SetFixedHeight(height);
         SetFixedHeight(height);
         
         
-        charPositionsDirty_ = true;
+        charLocationsDirty_ = true;
     }
     }
     else
     else
     {
     {
@@ -548,69 +563,64 @@ void Text::UpdateText()
     }
     }
 }
 }
 
 
-void Text::UpdateCharPositions()
+void Text::UpdateCharLocations()
 {
 {
-    if (font_)
+    // Remember the font face to see if it's still valid when it's time to render
+    FontFace* face = font_ ? font_->GetFace(fontSize_) : (FontFace*)0;
+    if (!face)
+        return;
+    fontFace_ = face;
+    
+    int rowHeight = (int)(rowSpacing_ * rowHeight_);
+    
+    // Store position & size of each character, and locations per texture page
+    unsigned numChars = unicodeText_.Size();
+    charLocations_.Resize(numChars + 1);
+    pageGlyphLocations_.Resize(face->GetTextures().Size());
+    for (unsigned i = 0; i < pageGlyphLocations_.Size(); ++i)
+        pageGlyphLocations_[i].Clear();
+
+    unsigned rowIndex = 0;
+    unsigned lastFilled = 0;
+    int x = GetRowStartPosition(rowIndex);
+    int y = 0;
+    for (unsigned i = 0; i < printText_.Size(); ++i)
     {
     {
-        // Remember the font face to see if it's still valid when it's time to render
-        FontFace* face = font_->GetFace(fontSize_);
-        if (!face)
-            return;
-        fontFace_ = face;
+        CharLocation loc;
+        loc.position_ = IntVector2(x, y);
         
         
-        int rowHeight = (int)(rowSpacing_ * rowHeight_);
-        
-        // Store position & size of each character, and locations per texture page
-        charPositions_.Resize(unicodeText_.Size() + 1);
-        charSizes_.Resize(unicodeText_.Size());
-        pageGlyphLocations_.Resize(face->GetTextures().Size());
-        for (unsigned i = 0; i < pageGlyphLocations_.Size(); ++i)
-            pageGlyphLocations_[i].Clear();
-
-        unsigned rowIndex = 0;
-        unsigned lastFilled = 0;
-        int x = GetRowStartPosition(rowIndex);
-        int y = 0;
-        for (unsigned i = 0; i < printText_.Size(); ++i)
+        unsigned c = printText_[i];
+        if (c != '\n')
         {
         {
-            IntVector2 pos(x, y);
-            
-            // Fill gaps in case characters were skipped from printing
-            for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
-                charPositions_[j] = pos;
-            
-            unsigned c = printText_[i];
-            if (c != '\n')
+            const FontGlyph* glyph = face->GetGlyph(c);
+            loc.size_ = IntVector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
+            if (glyph)
             {
             {
-                const FontGlyph* glyph = face->GetGlyph(c);
-                IntVector2 size(glyph ? glyph->advanceX_ : 0, rowHeight_);
-                for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
-                    charSizes_[j] = size;
-                if (glyph)
-                {
-                    // Store glyph's location for rendering. Verify that glyph page is valid
-                    if (glyph->page_ < pageGlyphLocations_.Size())
-                        pageGlyphLocations_[glyph->page_].Push(GlyphLocation(x, y, glyph));
-                    x += glyph->advanceX_;
-                    if (i < printText_.Size() - 1)
-                        x += face->GetKerning(c, printText_[i + 1]);
-                }
+                // Store glyph's location for rendering. Verify that glyph page is valid
+                if (glyph->page_ < pageGlyphLocations_.Size())
+                    pageGlyphLocations_[glyph->page_].Push(GlyphLocation(x, y, glyph));
+                x += glyph->advanceX_;
+                if (i < printText_.Size() - 1)
+                    x += face->GetKerning(c, printText_[i + 1]);
             }
             }
-            else
-            {
-                for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
-                    charSizes_[j] = IntVector2::ZERO;
-                x = GetRowStartPosition(++rowIndex);
-                y += rowHeight;
-            }
-            
-            lastFilled = printToText_[i] + 1;
         }
         }
-        // Store the ending position
-        charPositions_[unicodeText_.Size()] = IntVector2(x, y);
+        else
+        {
+            loc.size_ = IntVector2::ZERO;
+            x = GetRowStartPosition(++rowIndex);
+            y += rowHeight;
+        }
         
         
-        charPositionsDirty_ = false;
+        // Fill gaps in case characters were skipped from printing
+        for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
+            charLocations_[j] = loc;
+        lastFilled = printToText_[i] + 1;
     }
     }
+    // Store the ending position
+    charLocations_[numChars].position_ = IntVector2(x, y);
+    charLocations_[numChars].size_ = IntVector2::ZERO;
+    
+    charLocationsDirty_ = false;
 }
 }
 
 
 void Text::ValidateSelection()
 void Text::ValidateSelection()

+ 22 - 13
Source/Engine/UI/Text.h

@@ -41,6 +41,15 @@ enum TextEffect
     TE_STROKE
     TE_STROKE
 };
 };
 
 
+/// Cached character location and size within text. Used for queries related to text editing.
+struct CharLocation
+{
+    /// Position.
+    IntVector2 position_;
+    /// Size.
+    IntVector2 size_;
+};
+
 /// Glyph and its location within the text. Used when preparing text rendering.
 /// Glyph and its location within the text. Used when preparing text rendering.
 struct GlyphLocation
 struct GlyphLocation
 {
 {
@@ -137,12 +146,14 @@ public:
     int GetRowHeight() const { return rowHeight_; }
     int GetRowHeight() const { return rowHeight_; }
     /// Return number of rows.
     /// Return number of rows.
     unsigned GetNumRows() const { return rowWidths_.Size(); }
     unsigned GetNumRows() const { return rowWidths_.Size(); }
-    /// Return width of each row.
-    const PODVector<int>& GetRowWidths() const { return rowWidths_; }
-    /// Return position of each character.
-    const PODVector<IntVector2>& GetCharPositions();
-    /// Return size of each character.
-    const PODVector<IntVector2>& GetCharSizes();
+    /// Return number of characters.
+    unsigned GetNumChars() const { return unicodeText_.Size(); }
+    /// Return width of row by index.
+    int GetRowWidth(unsigned index) const;
+    /// Return position of character by index relative to the text element origin.
+    IntVector2 GetCharPosition(unsigned index);
+    /// Return size of character by index.
+    IntVector2 GetCharSize(unsigned index);
 
 
     /// Set text effect Z bias. Zero by default, adjusted only in 3D mode.
     /// Set text effect Z bias. Zero by default, adjusted only in 3D mode.
     void SetEffectDepthBias(float bias);
     void SetEffectDepthBias(float bias);
@@ -158,8 +169,8 @@ protected:
     virtual bool FilterImplicitAttributes(XMLElement& dest) const;
     virtual bool FilterImplicitAttributes(XMLElement& dest) const;
     /// Update text when text, font or spacing changed.
     /// Update text when text, font or spacing changed.
     void UpdateText();
     void UpdateText();
-    /// Update character positions.
-    void UpdateCharPositions();
+    /// Update cached character locations after text update, or when text alignment or indent has changed.
+    void UpdateCharLocations();
     /// Validate text selection to be within the text.
     /// Validate text selection to be within the text.
     void ValidateSelection();
     void ValidateSelection();
     /// Return row start X position.
     /// Return row start X position.
@@ -182,7 +193,7 @@ protected:
     /// Wordwrap mode.
     /// Wordwrap mode.
     bool wordWrap_;
     bool wordWrap_;
     /// Char positions dirty flag.
     /// Char positions dirty flag.
-    bool charPositionsDirty_;
+    bool charLocationsDirty_;
     /// Selection start.
     /// Selection start.
     unsigned selectionStart_;
     unsigned selectionStart_;
     /// Selection length.
     /// Selection length.
@@ -207,12 +218,10 @@ protected:
     PODVector<unsigned> printToText_;
     PODVector<unsigned> printToText_;
     /// Row widths.
     /// Row widths.
     PODVector<int> rowWidths_;
     PODVector<int> rowWidths_;
-    /// Positions of each character.
-    PODVector<IntVector2> charPositions_;
-    /// Sizes of each character.
-    PODVector<IntVector2> charSizes_;
     /// Glyph locations per each texture in the font.
     /// Glyph locations per each texture in the font.
     Vector<PODVector<GlyphLocation> > pageGlyphLocations_;
     Vector<PODVector<GlyphLocation> > pageGlyphLocations_;
+    /// Cached locations of each character in the text.
+    PODVector<CharLocation> charLocations_;
 };
 };
 
 
 }
 }