1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22
23 #include "../Precompiled.h"
24
25 #include "../Core/Context.h"
26 #include "../Core/Profiler.h"
27 #include "../Graphics/Texture2D.h"
28 #include "../IO/Log.h"
29 #include "../Resource/ResourceCache.h"
30 #include "../UI/Font.h"
31 #include "../UI/FontFace.h"
32 #include "../UI/Text.h"
33 #include "../Resource/Localization.h"
34 #include "../Resource/ResourceEvents.h"
35
36 #include "../DebugNew.h"
37
38 namespace Urho3D
39 {
40
41 const char* textEffects[] =
42 {
43 "None",
44 "Shadow",
45 "Stroke",
46 0
47 };
48
49 static const float MIN_ROW_SPACING = 0.5f;
50
51 extern const char* horizontalAlignments[];
52 extern const char* UI_CATEGORY;
53
Text(Context * context)54 Text::Text(Context* context) :
55 UIElement(context),
56 fontSize_(DEFAULT_FONT_SIZE),
57 textAlignment_(HA_LEFT),
58 rowSpacing_(1.0f),
59 wordWrap_(false),
60 autoLocalizable_(false),
61 charLocationsDirty_(true),
62 selectionStart_(0),
63 selectionLength_(0),
64 selectionColor_(Color::TRANSPARENT),
65 hoverColor_(Color::TRANSPARENT),
66 textEffect_(TE_NONE),
67 shadowOffset_(IntVector2(1, 1)),
68 strokeThickness_(1),
69 roundStroke_(false),
70 effectColor_(Color::BLACK),
71 effectDepthBias_(0.0f),
72 rowHeight_(0)
73 {
74 // By default Text does not derive opacity from parent elements
75 useDerivedOpacity_ = false;
76 }
77
~Text()78 Text::~Text()
79 {
80 }
81
RegisterObject(Context * context)82 void Text::RegisterObject(Context* context)
83 {
84 context->RegisterFactory<Text>(UI_CATEGORY);
85
86 URHO3D_COPY_BASE_ATTRIBUTES(UIElement);
87 URHO3D_UPDATE_ATTRIBUTE_DEFAULT_VALUE("Use Derived Opacity", false);
88 URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Font", GetFontAttr, SetFontAttr, ResourceRef, ResourceRef(Font::GetTypeStatic()), AM_FILE);
89 URHO3D_ATTRIBUTE("Font Size", float, fontSize_, DEFAULT_FONT_SIZE, AM_FILE);
90 URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Text", GetTextAttr, SetTextAttr, String, String::EMPTY, AM_FILE);
91 URHO3D_ENUM_ATTRIBUTE("Text Alignment", textAlignment_, horizontalAlignments, HA_LEFT, AM_FILE);
92 URHO3D_ATTRIBUTE("Row Spacing", float, rowSpacing_, 1.0f, AM_FILE);
93 URHO3D_ATTRIBUTE("Word Wrap", bool, wordWrap_, false, AM_FILE);
94 URHO3D_ACCESSOR_ATTRIBUTE("Auto Localizable", GetAutoLocalizable, SetAutoLocalizable, bool, false, AM_FILE);
95 URHO3D_ACCESSOR_ATTRIBUTE("Selection Color", GetSelectionColor, SetSelectionColor, Color, Color::TRANSPARENT, AM_FILE);
96 URHO3D_ACCESSOR_ATTRIBUTE("Hover Color", GetHoverColor, SetHoverColor, Color, Color::TRANSPARENT, AM_FILE);
97 URHO3D_ENUM_ATTRIBUTE("Text Effect", textEffect_, textEffects, TE_NONE, AM_FILE);
98 URHO3D_ATTRIBUTE("Shadow Offset", IntVector2, shadowOffset_, IntVector2(1, 1), AM_FILE);
99 URHO3D_ATTRIBUTE("Stroke Thickness", int, strokeThickness_, 1, AM_FILE);
100 URHO3D_ATTRIBUTE("Round Stroke", bool, roundStroke_, false, AM_FILE);
101 URHO3D_ACCESSOR_ATTRIBUTE("Effect Color", GetEffectColor, SetEffectColor, Color, Color::BLACK, AM_FILE);
102
103 // Change the default value for UseDerivedOpacity
104 context->GetAttribute<Text>("Use Derived Opacity")->defaultValue_ = false;
105 }
106
ApplyAttributes()107 void Text::ApplyAttributes()
108 {
109 UIElement::ApplyAttributes();
110
111 // Localize now if attributes were loaded out-of-order
112 if (autoLocalizable_ && stringId_.Length())
113 {
114 Localization* l10n = GetSubsystem<Localization>();
115 text_ = l10n->Get(stringId_);
116 }
117
118 DecodeToUnicode();
119
120 fontSize_ = Max(fontSize_, 1);
121 strokeThickness_ = Abs(strokeThickness_);
122 ValidateSelection();
123 UpdateText();
124 }
125
GetBatches(PODVector<UIBatch> & batches,PODVector<float> & vertexData,const IntRect & currentScissor)126 void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
127 {
128 FontFace* face = font_ ? font_->GetFace(fontSize_) : (FontFace*)0;
129 if (!face)
130 {
131 hovering_ = false;
132 return;
133 }
134
135 // If face has changed or char locations are not valid anymore, update before rendering
136 if (charLocationsDirty_ || !fontFace_ || face != fontFace_)
137 UpdateCharLocations();
138 // If face uses mutable glyphs mechanism, reacquire glyphs before rendering to make sure they are in the texture
139 else if (face->HasMutableGlyphs())
140 {
141 for (unsigned i = 0; i < printText_.Size(); ++i)
142 face->GetGlyph(printText_[i]);
143 }
144
145 // Hovering and/or whole selection batch
146 if ((hovering_ && hoverColor_.a_ > 0.0) || (selected_ && selectionColor_.a_ > 0.0f))
147 {
148 bool both = hovering_ && selected_ && hoverColor_.a_ > 0.0 && selectionColor_.a_ > 0.0f;
149 UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
150 batch.SetColor(both ? selectionColor_.Lerp(hoverColor_, 0.5f) :
151 (selected_ && selectionColor_.a_ > 0.0f ? selectionColor_ : hoverColor_));
152 batch.AddQuad(0, 0, GetWidth(), GetHeight(), 0, 0);
153 UIBatch::AddOrMerge(batch, batches);
154 }
155
156 // Partial selection batch
157 if (!selected_ && selectionLength_ && charLocations_.Size() >= selectionStart_ + selectionLength_ && selectionColor_.a_ > 0.0f)
158 {
159 UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
160 batch.SetColor(selectionColor_);
161
162 Vector2 currentStart = charLocations_[selectionStart_].position_;
163 Vector2 currentEnd = currentStart;
164 for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
165 {
166 // Check if row changes, and start a new quad in that case
167 if (charLocations_[i].size_ != Vector2::ZERO)
168 {
169 if (charLocations_[i].position_.y_ != currentStart.y_)
170 {
171 batch.AddQuad(currentStart.x_, currentStart.y_, currentEnd.x_ - currentStart.x_,
172 currentEnd.y_ - currentStart.y_, 0, 0);
173 currentStart = charLocations_[i].position_;
174 currentEnd = currentStart + charLocations_[i].size_;
175 }
176 else
177 {
178 currentEnd.x_ += charLocations_[i].size_.x_;
179 currentEnd.y_ = Max(currentStart.y_ + charLocations_[i].size_.y_, currentEnd.y_);
180 }
181 }
182 }
183 if (currentEnd != currentStart)
184 {
185 batch.AddQuad(currentStart.x_, currentStart.y_, currentEnd.x_ - currentStart.x_, currentEnd.y_ - currentStart.y_, 0, 0);
186 }
187
188 UIBatch::AddOrMerge(batch, batches);
189 }
190
191 // Text batch
192 TextEffect textEffect = font_->IsSDFFont() ? TE_NONE : textEffect_;
193 const Vector<SharedPtr<Texture2D> >& textures = face->GetTextures();
194 for (unsigned n = 0; n < textures.Size() && n < pageGlyphLocations_.Size(); ++n)
195 {
196 // One batch per texture/page
197 UIBatch pageBatch(this, BLEND_ALPHA, currentScissor, textures[n], &vertexData);
198
199 const PODVector<GlyphLocation>& pageGlyphLocation = pageGlyphLocations_[n];
200
201 switch (textEffect)
202 {
203 case TE_NONE:
204 ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
205 break;
206
207 case TE_SHADOW:
208 ConstructBatch(pageBatch, pageGlyphLocation, shadowOffset_.x_, shadowOffset_.y_, &effectColor_, effectDepthBias_);
209 ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
210 break;
211
212 case TE_STROKE:
213 if (roundStroke_)
214 {
215 // Samples should be even or glyph may be redrawn in wrong x y pos making stroke corners rough
216 // Adding to thickness helps with thickness of 1 not having enought samples for this formula
217 // or certain fonts with reflex corners requiring more glyph samples for a smooth stroke when large
218 int thickness = Min(strokeThickness_, fontSize_);
219 int samples = thickness * thickness + (thickness % 2 == 0 ? 4 : 3);
220 float angle = 360.f / samples;
221 float floatThickness = (float)thickness;
222 for (int i = 0; i < samples; ++i)
223 {
224 float x = Cos(angle * i) * floatThickness;
225 float y = Sin(angle * i) * floatThickness;
226 ConstructBatch(pageBatch, pageGlyphLocation, x, y, &effectColor_, effectDepthBias_);
227 }
228 }
229 else
230 {
231 int thickness = Min(strokeThickness_, fontSize_);
232 int x, y;
233 for (x = -thickness; x <= thickness; ++x)
234 {
235 for (y = -thickness; y <= thickness; ++y)
236 {
237 // Don't draw glyphs that aren't on the edges
238 if (x > -thickness && x < thickness &&
239 y > -thickness && y < thickness)
240 continue;
241
242 ConstructBatch(pageBatch, pageGlyphLocation, x, y, &effectColor_, effectDepthBias_);
243 }
244 }
245 }
246 ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
247 break;
248 }
249
250 UIBatch::AddOrMerge(pageBatch, batches);
251 }
252
253 // Reset hovering for next frame
254 hovering_ = false;
255 }
256
OnResize(const IntVector2 & newSize,const IntVector2 & delta)257 void Text::OnResize(const IntVector2& newSize, const IntVector2& delta)
258 {
259 if (wordWrap_)
260 UpdateText(true);
261 else
262 charLocationsDirty_ = true;
263 }
264
OnIndentSet()265 void Text::OnIndentSet()
266 {
267 charLocationsDirty_ = true;
268 }
269
SetFont(const String & fontName,int size)270 bool Text::SetFont(const String& fontName, int size)
271 {
272 ResourceCache* cache = GetSubsystem<ResourceCache>();
273 return SetFont(cache->GetResource<Font>(fontName), size);
274 }
275
SetFont(Font * font,float size)276 bool Text::SetFont(Font* font, float size)
277 {
278 if (!font)
279 {
280 URHO3D_LOGERROR("Null font for Text");
281 return false;
282 }
283
284 if (font != font_ || size != fontSize_)
285 {
286 font_ = font;
287 fontSize_ = Max(size, 1);
288 UpdateText();
289 }
290
291 return true;
292 }
293
SetFontSize(float size)294 bool Text::SetFontSize(float size)
295 {
296 // Initial font must be set
297 if (!font_)
298 return false;
299 else
300 return SetFont(font_, size);
301 }
302
DecodeToUnicode()303 void Text::DecodeToUnicode()
304 {
305 unicodeText_.Clear();
306 for (unsigned i = 0; i < text_.Length();)
307 unicodeText_.Push(text_.NextUTF8Char(i));
308 }
309
SetText(const String & text)310 void Text::SetText(const String& text)
311 {
312 if (autoLocalizable_)
313 {
314 stringId_ = text;
315 Localization* l10n = GetSubsystem<Localization>();
316 text_ = l10n->Get(stringId_);
317 }
318 else
319 {
320 text_ = text;
321 }
322
323 DecodeToUnicode();
324 ValidateSelection();
325 UpdateText();
326 }
327
SetTextAlignment(HorizontalAlignment align)328 void Text::SetTextAlignment(HorizontalAlignment align)
329 {
330 if (align != textAlignment_)
331 {
332 textAlignment_ = align;
333 charLocationsDirty_ = true;
334 }
335 }
336
SetRowSpacing(float spacing)337 void Text::SetRowSpacing(float spacing)
338 {
339 if (spacing != rowSpacing_)
340 {
341 rowSpacing_ = Max(spacing, MIN_ROW_SPACING);
342 UpdateText();
343 }
344 }
345
SetWordwrap(bool enable)346 void Text::SetWordwrap(bool enable)
347 {
348 if (enable != wordWrap_)
349 {
350 wordWrap_ = enable;
351 UpdateText();
352 }
353 }
354
SetAutoLocalizable(bool enable)355 void Text::SetAutoLocalizable(bool enable)
356 {
357 if (enable != autoLocalizable_)
358 {
359 autoLocalizable_ = enable;
360 if (enable)
361 {
362 stringId_ = text_;
363 Localization* l10n = GetSubsystem<Localization>();
364 text_ = l10n->Get(stringId_);
365 SubscribeToEvent(E_CHANGELANGUAGE, URHO3D_HANDLER(Text, HandleChangeLanguage));
366 }
367 else
368 {
369 text_ = stringId_;
370 stringId_ = "";
371 UnsubscribeFromEvent(E_CHANGELANGUAGE);
372 }
373 DecodeToUnicode();
374 ValidateSelection();
375 UpdateText();
376 }
377 }
378
HandleChangeLanguage(StringHash eventType,VariantMap & eventData)379 void Text::HandleChangeLanguage(StringHash eventType, VariantMap& eventData)
380 {
381 Localization* l10n = GetSubsystem<Localization>();
382 text_ = l10n->Get(stringId_);
383 DecodeToUnicode();
384 ValidateSelection();
385 UpdateText();
386 }
387
SetSelection(unsigned start,unsigned length)388 void Text::SetSelection(unsigned start, unsigned length)
389 {
390 selectionStart_ = start;
391 selectionLength_ = length;
392 ValidateSelection();
393 }
394
ClearSelection()395 void Text::ClearSelection()
396 {
397 selectionStart_ = 0;
398 selectionLength_ = 0;
399 }
400
SetSelectionColor(const Color & color)401 void Text::SetSelectionColor(const Color& color)
402 {
403 selectionColor_ = color;
404 }
405
SetHoverColor(const Color & color)406 void Text::SetHoverColor(const Color& color)
407 {
408 hoverColor_ = color;
409 }
410
SetTextEffect(TextEffect textEffect)411 void Text::SetTextEffect(TextEffect textEffect)
412 {
413 textEffect_ = textEffect;
414 }
415
SetEffectShadowOffset(const IntVector2 & offset)416 void Text::SetEffectShadowOffset(const IntVector2& offset)
417 {
418 shadowOffset_ = offset;
419 }
420
SetEffectStrokeThickness(int thickness)421 void Text::SetEffectStrokeThickness(int thickness)
422 {
423 strokeThickness_ = Abs(thickness);
424 }
425
SetEffectRoundStroke(bool roundStroke)426 void Text::SetEffectRoundStroke(bool roundStroke)
427 {
428 roundStroke_ = roundStroke;
429 }
430
SetEffectColor(const Color & effectColor)431 void Text::SetEffectColor(const Color& effectColor)
432 {
433 effectColor_ = effectColor;
434 }
435
SetEffectDepthBias(float bias)436 void Text::SetEffectDepthBias(float bias)
437 {
438 effectDepthBias_ = bias;
439 }
440
GetRowWidth(unsigned index) const441 float Text::GetRowWidth(unsigned index) const
442 {
443 return index < rowWidths_.Size() ? rowWidths_[index] : 0;
444 }
445
GetCharPosition(unsigned index)446 Vector2 Text::GetCharPosition(unsigned index)
447 {
448 if (charLocationsDirty_)
449 UpdateCharLocations();
450 if (charLocations_.Empty())
451 return Vector2::ZERO;
452 // For convenience, return the position of the text ending if index exceeded
453 if (index > charLocations_.Size() - 1)
454 index = charLocations_.Size() - 1;
455 return charLocations_[index].position_;
456 }
457
GetCharSize(unsigned index)458 Vector2 Text::GetCharSize(unsigned index)
459 {
460 if (charLocationsDirty_)
461 UpdateCharLocations();
462 if (charLocations_.Size() < 2)
463 return Vector2::ZERO;
464 // For convenience, return the size of the last char if index exceeded (last size entry is zero)
465 if (index > charLocations_.Size() - 2)
466 index = charLocations_.Size() - 2;
467 return charLocations_[index].size_;
468 }
469
SetFontAttr(const ResourceRef & value)470 void Text::SetFontAttr(const ResourceRef& value)
471 {
472 ResourceCache* cache = GetSubsystem<ResourceCache>();
473 font_ = cache->GetResource<Font>(value.name_);
474 }
475
GetFontAttr() const476 ResourceRef Text::GetFontAttr() const
477 {
478 return GetResourceRef(font_, Font::GetTypeStatic());
479 }
480
SetTextAttr(const String & value)481 void Text::SetTextAttr(const String& value)
482 {
483 text_ = value;
484 if (autoLocalizable_)
485 stringId_ = value;
486 }
487
GetTextAttr() const488 String Text::GetTextAttr() const
489 {
490 if (autoLocalizable_ && stringId_.Length())
491 return stringId_;
492 else
493 return text_;
494 }
495
FilterImplicitAttributes(XMLElement & dest) const496 bool Text::FilterImplicitAttributes(XMLElement& dest) const
497 {
498 if (!UIElement::FilterImplicitAttributes(dest))
499 return false;
500
501 if (!IsFixedWidth())
502 {
503 if (!RemoveChildXML(dest, "Size"))
504 return false;
505 if (!RemoveChildXML(dest, "Min Size"))
506 return false;
507 if (!RemoveChildXML(dest, "Max Size"))
508 return false;
509 }
510
511 return true;
512 }
513
UpdateText(bool onResize)514 void Text::UpdateText(bool onResize)
515 {
516 rowWidths_.Clear();
517 printText_.Clear();
518
519 if (font_)
520 {
521 FontFace* face = font_->GetFace(fontSize_);
522 if (!face)
523 return;
524
525 rowHeight_ = face->GetRowHeight();
526
527 int width = 0;
528 int height = 0;
529 int rowWidth = 0;
530 int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
531
532 // First see if the text must be split up
533 if (!wordWrap_)
534 {
535 printText_ = unicodeText_;
536 printToText_.Resize(printText_.Size());
537 for (unsigned i = 0; i < printText_.Size(); ++i)
538 printToText_[i] = i;
539 }
540 else
541 {
542 int maxWidth = GetWidth();
543 unsigned nextBreak = 0;
544 unsigned lineStart = 0;
545 printToText_.Clear();
546
547 for (unsigned i = 0; i < unicodeText_.Size(); ++i)
548 {
549 unsigned j;
550 unsigned c = unicodeText_[i];
551
552 if (c != '\n')
553 {
554 bool ok = true;
555
556 if (nextBreak <= i)
557 {
558 int futureRowWidth = rowWidth;
559 for (j = i; j < unicodeText_.Size(); ++j)
560 {
561 unsigned d = unicodeText_[j];
562 if (d == ' ' || d == '\n')
563 {
564 nextBreak = j;
565 break;
566 }
567 const FontGlyph* glyph = face->GetGlyph(d);
568 if (glyph)
569 {
570 futureRowWidth += glyph->advanceX_;
571 if (j < unicodeText_.Size() - 1)
572 futureRowWidth += face->GetKerning(d, unicodeText_[j + 1]);
573 }
574 if (d == '-' && futureRowWidth <= maxWidth)
575 {
576 nextBreak = j + 1;
577 break;
578 }
579 if (futureRowWidth > maxWidth)
580 {
581 ok = false;
582 break;
583 }
584 }
585 }
586
587 if (!ok)
588 {
589 // If did not find any breaks on the line, copy until j, or at least 1 char, to prevent infinite loop
590 if (nextBreak == lineStart)
591 {
592 while (i < j)
593 {
594 printText_.Push(unicodeText_[i]);
595 printToText_.Push(i);
596 ++i;
597 }
598 }
599 // Eliminate spaces that have been copied before the forced break
600 while (printText_.Size() && printText_.Back() == ' ')
601 {
602 printText_.Pop();
603 printToText_.Pop();
604 }
605 printText_.Push('\n');
606 printToText_.Push(Min(i, unicodeText_.Size() - 1));
607 rowWidth = 0;
608 nextBreak = lineStart = i;
609 }
610
611 if (i < unicodeText_.Size())
612 {
613 // When copying a space, position is allowed to be over row width
614 c = unicodeText_[i];
615 const FontGlyph* glyph = face->GetGlyph(c);
616 if (glyph)
617 {
618 rowWidth += glyph->advanceX_;
619 if (i < unicodeText_.Size() - 1)
620 rowWidth += face->GetKerning(c, unicodeText_[i + 1]);
621 }
622 if (rowWidth <= maxWidth)
623 {
624 printText_.Push(c);
625 printToText_.Push(i);
626 }
627 }
628 }
629 else
630 {
631 printText_.Push('\n');
632 printToText_.Push(Min(i, unicodeText_.Size() - 1));
633 rowWidth = 0;
634 nextBreak = lineStart = i;
635 }
636 }
637 }
638
639 rowWidth = 0;
640
641 for (unsigned i = 0; i < printText_.Size(); ++i)
642 {
643 unsigned c = printText_[i];
644
645 if (c != '\n')
646 {
647 const FontGlyph* glyph = face->GetGlyph(c);
648 if (glyph)
649 {
650 rowWidth += glyph->advanceX_;
651 if (i < printText_.Size() - 1)
652 rowWidth += face->GetKerning(c, printText_[i + 1]);
653 }
654 }
655 else
656 {
657 width = Max(width, rowWidth);
658 height += rowHeight;
659 rowWidths_.Push(rowWidth);
660 rowWidth = 0;
661 }
662 }
663
664 if (rowWidth)
665 {
666 width = Max(width, rowWidth);
667 height += rowHeight;
668 rowWidths_.Push(rowWidth);
669 }
670
671 // Set at least one row height even if text is empty
672 if (!height)
673 height = rowHeight;
674
675 // Set minimum and current size according to the text size, but respect fixed width if set
676 if (!IsFixedWidth())
677 {
678 if (wordWrap_)
679 SetMinWidth(0);
680 else
681 {
682 SetMinWidth(width);
683 SetWidth(width);
684 }
685 }
686 SetFixedHeight(height);
687
688 charLocationsDirty_ = true;
689 }
690 else
691 {
692 // No font, nothing to render
693 pageGlyphLocations_.Clear();
694 }
695
696 // If wordwrap is on, parent may need layout update to correct for overshoot in size. However, do not do this when the
697 // update is a response to resize, as that could cause infinite recursion
698 if (wordWrap_ && !onResize)
699 {
700 UIElement* parent = GetParent();
701 if (parent && parent->GetLayoutMode() != LM_FREE)
702 parent->UpdateLayout();
703 }
704 }
705
UpdateCharLocations()706 void Text::UpdateCharLocations()
707 {
708 // Remember the font face to see if it's still valid when it's time to render
709 FontFace* face = font_ ? font_->GetFace(fontSize_) : (FontFace*)0;
710 if (!face)
711 return;
712 fontFace_ = face;
713
714 int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
715
716 // Store position & size of each character, and locations per texture page
717 unsigned numChars = unicodeText_.Size();
718 charLocations_.Resize(numChars + 1);
719 pageGlyphLocations_.Resize(face->GetTextures().Size());
720 for (unsigned i = 0; i < pageGlyphLocations_.Size(); ++i)
721 pageGlyphLocations_[i].Clear();
722
723 IntVector2 offset = font_->GetTotalGlyphOffset(fontSize_);
724
725 unsigned rowIndex = 0;
726 unsigned lastFilled = 0;
727 float x = floor(GetRowStartPosition(rowIndex) + offset.x_ + 0.5f);
728 float y = floor(offset.y_ + 0.5f);
729
730 for (unsigned i = 0; i < printText_.Size(); ++i)
731 {
732 CharLocation loc;
733 loc.position_ = Vector2(x, y);
734
735 unsigned c = printText_[i];
736 if (c != '\n')
737 {
738 const FontGlyph* glyph = face->GetGlyph(c);
739 loc.size_ = Vector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
740 if (glyph)
741 {
742 // Store glyph's location for rendering. Verify that glyph page is valid
743 if (glyph->page_ < pageGlyphLocations_.Size())
744 pageGlyphLocations_[glyph->page_].Push(GlyphLocation(x, y, glyph));
745 x += glyph->advanceX_;
746 if (i < printText_.Size() - 1)
747 x += face->GetKerning(c, printText_[i + 1]);
748 }
749 }
750 else
751 {
752 loc.size_ = Vector2::ZERO;
753 x = GetRowStartPosition(++rowIndex);
754 y += rowHeight;
755 }
756
757 if (lastFilled > printToText_[i])
758 lastFilled = printToText_[i];
759
760 // Fill gaps in case characters were skipped from printing
761 for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
762 charLocations_[j] = loc;
763 lastFilled = printToText_[i] + 1;
764 }
765 // Store the ending position
766 charLocations_[numChars].position_ = Vector2(x, y);
767 charLocations_[numChars].size_ = Vector2::ZERO;
768
769 charLocationsDirty_ = false;
770 }
771
ValidateSelection()772 void Text::ValidateSelection()
773 {
774 unsigned textLength = unicodeText_.Size();
775
776 if (textLength)
777 {
778 if (selectionStart_ >= textLength)
779 selectionStart_ = textLength - 1;
780 if (selectionStart_ + selectionLength_ > textLength)
781 selectionLength_ = textLength - selectionStart_;
782 }
783 else
784 {
785 selectionStart_ = 0;
786 selectionLength_ = 0;
787 }
788 }
789
GetRowStartPosition(unsigned rowIndex) const790 int Text::GetRowStartPosition(unsigned rowIndex) const
791 {
792 float rowWidth = 0;
793
794 if (rowIndex < rowWidths_.Size())
795 rowWidth = rowWidths_[rowIndex];
796
797 int ret = GetIndentWidth();
798
799 switch (textAlignment_)
800 {
801 case HA_LEFT:
802 break;
803 case HA_CENTER:
804 ret += (GetSize().x_ - rowWidth) / 2;
805 break;
806 case HA_RIGHT:
807 ret += GetSize().x_ - rowWidth;
808 break;
809 }
810
811 return ret;
812 }
813
ConstructBatch(UIBatch & pageBatch,const PODVector<GlyphLocation> & pageGlyphLocation,float dx,float dy,Color * color,float depthBias)814 void Text::ConstructBatch(UIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, float dx, float dy, Color* color,
815 float depthBias)
816 {
817 unsigned startDataSize = pageBatch.vertexData_->Size();
818
819 if (!color)
820 pageBatch.SetDefaultColor();
821 else
822 pageBatch.SetColor(*color);
823
824 for (unsigned i = 0; i < pageGlyphLocation.Size(); ++i)
825 {
826 const GlyphLocation& glyphLocation = pageGlyphLocation[i];
827 const FontGlyph& glyph = *glyphLocation.glyph_;
828 pageBatch.AddQuad(dx + glyphLocation.x_ + glyph.offsetX_, dy + glyphLocation.y_ + glyph.offsetY_, glyph.width_,
829 glyph.height_, glyph.x_, glyph.y_, glyph.texWidth_, glyph.texHeight_);
830 }
831
832 if (depthBias != 0.0f)
833 {
834 unsigned dataSize = pageBatch.vertexData_->Size();
835 for (unsigned i = startDataSize; i < dataSize; i += UI_VERTEX_SIZE)
836 pageBatch.vertexData_->At(i + 2) += depthBias;
837 }
838 }
839
840 }
841