TDME2  1.9.200
GUIStyledTextNode.cpp
Go to the documentation of this file.
2 
3 #include <list>
4 #include <string>
5 #include <string_view>
6 
7 #include <tdme/tdme.h>
8 #include <tdme/engine/Texture.h>
10 #include <tdme/engine/Engine.h>
23 #include <tdme/gui/GUI.h>
24 #include <tdme/gui/GUIParser.h>
26 #include <tdme/utilities/Console.h>
28 #include <tdme/utilities/Float.h>
33 
34 using std::list;
35 using std::string;
36 using std::string_view;
37 using std::to_string;
38 
40 
56 using tdme::gui::GUI;
66 
67 GUIStyledTextNode::GUIStyledTextNode(
68  GUIScreenNode* screenNode,
69  GUIParentNode* parentNode,
70  const string& id,
71  GUINode_Flow* flow,
72  const GUINode_Alignments& alignments,
73  const GUINode_RequestedConstraints& requestedConstraints,
74  const GUIColor& backgroundColor,
75  const string& backgroundImage,
76  const GUINode_Scale9Grid& backgroundImageScale9Grid,
77  const GUIColor& backgroundImageEffectColorMul,
78  const GUIColor& backgroundImageEffectColorAdd,
79  const GUINode_Border& border,
80  const GUINode_Padding& padding,
81  const GUINodeConditions& showOn,
82  const GUINodeConditions& hideOn,
83  const string& tooltip,
84  bool editable,
85  bool preformatted,
86  const string& font,
87  int size,
88  const string& color,
89  const MutableString& text
90 ):
91  GUINode(screenNode, parentNode, id, flow, alignments, requestedConstraints, backgroundColor, backgroundImage, backgroundImageScale9Grid, backgroundImageEffectColorMul, backgroundImageEffectColorAdd, border, padding, showOn, hideOn, tooltip)
92 {
93  this->font = font.empty() == true?nullptr:screenNode->getFont(font, size);
94  this->size = size;
95  this->color = color.empty() == true || color.length() == 0?GUIColor():GUIColor(color);
96  this->autoWidth = 0;
97  this->autoHeight = 0;
98  this->parentOffsetsChanged = true;
99  this->parentXOffsetLast = 0.0f;
100  this->parentYOffsetLast = 0.0f;
101  this->startRenderY = 0;
102  this->charStartIdx = 0;
103  this->charEndIdx = text.size();
104  this->widthLast = -1;
105  this->heightLast = -1;
106  this->startTextStyleIdx = -1;
107  this->editable = editable;
108  this->preformatted = preformatted;
109  this->selectionBackgroundColor = GUIColor(GUIParser::getEngineThemeProperties()->get("color.focus", "#ff0000"));
110  if (this->font != nullptr) this->font->initialize();
111  setText(text);
112 }
113 
117 }
118 
122 }
123 
127 }
128 
132 }
133 
134 void GUIStyledTextNode::removeText(int32_t idx, int32_t count) {
135  // TODO: we have new node dimension after remove
136  text.remove(idx, count, &count);
137  idx = text.getUtf8BinaryIndex(idx);
138  auto adaptNextStyles = false;
139  for (auto i = 0; i < styles.size(); i++) {
140  auto& style = styles[i];
141  // adapting styles after specific style change for all succeeding styles
142  if (adaptNextStyles == true) {
143  style.startIdx-= count;
144  style.endIdx-= count;
145  if (style.startIdx < 0 && style.endIdx < 0) {
146  styles.erase(styles.begin() + i);
147  i--;
148  continue;
149  }
150  if (style.startIdx < 0) style.startIdx = 0;
151  if (style.endIdx < 0) style.endIdx = 0;
152  } else
153  // adapting styles for all succeeding styles
154  if (idx < style.startIdx) {
155  style.startIdx-= count;
156  style.endIdx-= count;
157  if (style.startIdx < 0 && style.endIdx < 0) {
158  styles.erase(styles.begin() + i);
159  i--;
160  continue;
161  }
162  if (style.startIdx < 0) style.startIdx = 0;
163  if (style.endIdx < 0) style.endIdx = 0;
164  adaptNextStyles = true;
165  } else {
166  // our index >= style start and index <= style end
167  if (idx <= style.endIdx) {
168  // idx < end idx
169  style.endIdx-= count;
170  if (style.endIdx < 0) {
171  styles.erase(styles.begin() + i);
172  i--;
173  continue;
174  }
175  if (style.endIdx < 0) style.endIdx = 0;
176  adaptNextStyles = true;
177  }
178  }
179  }
180  charEndIdx = text.size();
181  startTextStyleIdx = -1;
182  // TODO: this can be optimized later
184  //
186 }
187 
188 void GUIStyledTextNode::insertText(int32_t idx, int c) {
189  auto s = Character::toString(c);
190  text.insert(idx, s);
191  idx = text.getUtf8BinaryIndex(idx);
192  auto count = s.size();
193  auto adaptNextStyles = false;
194  for (auto& style: styles) {
195  // adapting styles after specific style change for all succeeding styles
196  if (adaptNextStyles == true) {
197  style.startIdx+= count;
198  style.endIdx+= count;
199  } else
200  // adapting styles for all succeeding styles
201  if (idx < style.startIdx) {
202  style.startIdx+= count;
203  style.endIdx+= count;
204  adaptNextStyles = true;
205  } else {
206  // our index >= style start and index <= style end
207  if (idx <= style.endIdx) {
208  // idx < end idx
209  style.endIdx+= count;
210  adaptNextStyles = true;
211  }
212  }
213  }
214  charEndIdx = text.size();
215  startTextStyleIdx = -1;
216  // TODO: this can be optimized later
218  //
220 }
221 
222 void GUIStyledTextNode::insertText(int32_t idx, const string& s) {
223  // TODO: we have new node dimension after remove
224  text.insert(idx, s);
225  idx = text.getUtf8BinaryIndex(idx);
226  auto count = s.size();
227  auto adaptNextStyles = false;
228  for (auto& style: styles) {
229  // adapting styles after specific style change for all succeeding styles
230  if (adaptNextStyles == true) {
231  style.startIdx+= count;
232  style.endIdx+= count;
233  } else
234  // adapting styles for all succeeding styles
235  if (idx < style.startIdx) {
236  style.startIdx+= count;
237  style.endIdx+= count;
238  adaptNextStyles = true;
239  } else {
240  // our index >= style start and index <= style end
241  if (idx <= style.endIdx) {
242  // idx < end idx
243  style.endIdx+= count;
244  adaptNextStyles = true;
245  }
246  }
247  }
248  charEndIdx = text.size();
249  startTextStyleIdx = -1;
250  // TODO: this can be optimized later
252  //
254 }
255 
256 void GUIStyledTextNode::scrollToIndex(int cursorIndex) {
257  // no font, exit
258  if (font == nullptr) return;
259 
260  //
261  auto yBefore = 0.0f;
262  auto y = 0.0f;
263  auto textStyleIdx = 0;
264  auto u8It = text.getUTF8CharacterIterator();
265  while (u8It.hasNext() == true) {
266  //
267  determineNextLineConstraints(u8It, text.size(), textStyleIdx);
268 
269  //
270  for (const auto& lineConstraintsEntity: lineConstraints) {
271  y+= lineConstraintsEntity.height;
272  }
273 
274  //
275  auto reachedCursorIndex = false;
276  for (auto idx: lineCharIdxs) {
277  if (idx == cursorIndex) {
278  reachedCursorIndex = true;
279  break;
280  }
281  }
282  if (reachedCursorIndex == true) break;
283 
284  //
285  line.clear();
286  lineCharBinaryIdxs.clear();
287  lineCharIdxs.clear();
288  lineConstraints.clear();
289 
290  //
291  yBefore = y;
292  }
293 
294  //
295  auto renderOffsetY = parentNode->getChildrenRenderOffsetY();
296 
297  // scroll up if required
298  if (renderOffsetY > yBefore) {
299  parentOffsetsChanged = true;
301  }
302 
303  // scroll down if required
304  if (renderOffsetY + parentNode->getComputedConstraints().height < y) {
305  parentOffsetsChanged = true;
307  }
308 }
309 
311  // no font, exit
312  if (font == nullptr) return;
313 
314  //
315  auto styledTextController = required_dynamic_cast<GUIStyledTextNodeController*>(getController());
316  scrollToIndex(styledTextController->getIndex());
317 }
318 
320  // no font, exit
321  if (font == nullptr) return;
322 
323  //
324  auto styledTextController = required_dynamic_cast<GUIStyledTextNodeController*>(getController());
325  scrollToIndex(styledTextController->getSelectionIndex());
326 }
327 
329  //
330  scrollToIndex();
331 
332  // no font, exit
333  if (font == nullptr) return -1;
334 
335  //
336  auto styledTextController = required_dynamic_cast<GUIStyledTextNodeController*>(getController());
337  auto cursorIndex = styledTextController->getIndex();
338  auto visibleHeight = parentNode->getComputedConstraints().height;
339  auto y = 0.0f;
340  auto textStyleIdx = 0;
341  auto reachedCursorIndex = false;
342  struct LineInfo {
343  LineInfo(
344  int charIdx,
345  float y
346  ):
347  charIdx(charIdx),
348  y(y)
349  {}
350  int charIdx;
351  float y;
352  };
353  vector<LineInfo> lines;
354  auto u8It = text.getUTF8CharacterIterator();
355  while (u8It.hasNext() == true) {
356  //
357  determineNextLineConstraints(u8It, text.size(), textStyleIdx);
358 
359  // did we reach our corsor index
360  if (reachedCursorIndex == false) {
361  for (auto idx: lineCharIdxs) {
362  if (idx == cursorIndex) {
363  reachedCursorIndex = true;
364  break;
365  }
366  }
367  }
368 
369  // if reached cursor index try to find index of a page down height
370  for (const auto& lineConstraintsEntity: lineConstraints) {
371  lines.emplace_back(
372  lineCharIdxs[0],
373  y
374  );
375  y+= lineConstraintsEntity.height;
376  }
377 
378  //
379  line.clear();
380  lineCharBinaryIdxs.clear();
381  lineCharIdxs.clear();
382  lineConstraints.clear();
383 
384  //
385  if (reachedCursorIndex == true) break;
386  }
387 
388  // scroll down
389  if (visibleHeight < autoHeight) {
390  auto renderOffsetY = parentNode->getChildrenRenderOffsetY();
391  parentOffsetsChanged = true;
392  parentNode->setChildrenRenderOffsetY(Math::max(renderOffsetY - visibleHeight, 0.0f));
393  }
394 
395  // determine cursor index
396  for (int i = lines.size() - 1; i >= 0; i--) {
397  const auto& line = lines[i];
398  if (y - line.y >= visibleHeight) {
399  return line.charIdx;
400  }
401  }
402 
403  // otherwise fallback to previous value
404  return 0;
405 }
406 
408  //
409  scrollToIndex();
410 
411  // no font, exit
412  if (font == nullptr) return -1;
413 
414  //
415  auto styledTextController = required_dynamic_cast<GUIStyledTextNodeController*>(getController());
416  auto cursorIndex = styledTextController->getIndex();
417  auto visibleHeight = parentNode->getComputedConstraints().height;
418  auto y = 0.0f;
419  auto textStyleIdx = 0;
420  auto reachedCursorIndex = false;
421  auto finished = false;
422  auto u8It = text.getUTF8CharacterIterator();
423  while (u8It.hasNext() == true) {
424  //
425  determineNextLineConstraints(u8It, text.size(), textStyleIdx);
426 
427  // did we reach our corsor index
428  if (reachedCursorIndex == false) {
429  for (auto idx: lineCharIdxs) {
430  if (idx == cursorIndex) {
431  reachedCursorIndex = true;
432  break;
433  }
434  }
435  // then reset Y
436  if (reachedCursorIndex == true) {
437  y = 0.0f;
438  }
439  }
440 
441  // if reached cursor index try to find index of a page down height
442  if (reachedCursorIndex == true) {
443  for (const auto& lineConstraintsEntity: lineConstraints) {
444  y+= lineConstraintsEntity.height;
445  if (y >= visibleHeight) {
446  cursorIndex = lineCharIdxs[0];
447  finished = true;
448  break;
449  }
450  }
451  }
452 
453  //
454  line.clear();
455  lineCharBinaryIdxs.clear();
456  lineCharIdxs.clear();
457  lineConstraints.clear();
458 
459  //
460  if (finished == true) break;
461  }
462 
463  // scroll down
464  if (visibleHeight < autoHeight) {
465  auto renderOffsetY = parentNode->getChildrenRenderOffsetY();
466  parentOffsetsChanged = true;
467  parentNode->setChildrenRenderOffsetY(Math::min(renderOffsetY + visibleHeight, static_cast<float>(autoHeight - visibleHeight)));
468  }
469 
470  //
471  if (finished == true) {
472  return cursorIndex;
473  } else {
474  return u8It.getCharacterPosition() - 1;
475  }
476 }
477 
479 {
480  return "styledtext";
481 }
482 
484 {
485  return true;
486 }
487 
489 {
491  return font != nullptr?autoWidth + border.left + border.right + padding.left + padding.right:0;
492  } else {
493  return computedConstraints.width;
494  }
495 }
496 
499  return font != nullptr?autoHeight + border.top + border.bottom + padding.top + padding.bottom:0;
500  } else {
502  }
503 }
504 
506  // If fixed width requested and no computed constraints yet, abort
508  // width did not change, but relayout has been requested
510  // no font, exit
511  if (font == nullptr) return;
512 
513  //
514  autoWidth = 0;
515  autoHeight = 0;
516 
517  //
518  auto textStyleIdx = 0;
519  auto u8It = text.getUTF8CharacterIterator();
520  while (u8It.hasNext() == true) {
521  //
522  determineNextLineConstraints(u8It, text.size(), textStyleIdx);
523 
524  //
525  for (const auto& lineConstraintsEntity: lineConstraints) {
526  if (lineConstraintsEntity.width > autoWidth) autoWidth = lineConstraintsEntity.width;
527  autoHeight+= lineConstraintsEntity.height;
528  }
529 
530  //
531  line.clear();
532  lineCharBinaryIdxs.clear();
533  lineCharIdxs.clear();
534  lineConstraints.clear();
535  }
536 
537  //
540 }
541 
544 
545  //
546  this->parentOffsetsChanged = true;
547 }
548 
550  this->parentOffsetsChanged = true;
551  this->parentXOffsetLast = 0.0f;
552  this->parentYOffsetLast = 0.0f;
553  this->charStartIdx = 0;
554  this->startRenderY = 0;
555  this->widthLast = -1;
556  this->heightLast = -1;
557  this->startTextStyleIdx = -1;
559  unsetStyles();
560  this->text.reset();
561  /*
562  // Currently supported BBCode like tags
563  [font=schriftart]Text[/font]
564  [color=farbe]farbiger Text[/color]
565  [url=http://example.com/]Linktext[/url]
566  [image=horizontal-scale:50%|0.5,vertical-scale:50%|0.5,width:100,height:100,effect-color-mul:#ff0000ff,effect-color-add:#ff000000]example.com/bild.jpg[/image]
567  */
568  string styleFont;
569  int styleSize = size;
570  string styleColor;
571  string styleUrl;
572  string styleImage;
573  //
574  auto parseStyle = false;
575  auto parseImage = false;
576  int imageWidth = -1;
577  int imageHeight = -1;
578  float imageHorizontalScale = 1.0f;
579  float imageVerticalScale = 1.0f;
580  GUIColor imageEffectColorMul = GUIColor::GUICOLOR_EFFECT_COLOR_MUL;
581  GUIColor imageEffectColorAdd = GUIColor::GUICOLOR_EFFECT_COLOR_ADD;
582  string currentStyle;
583  int styleStartIdx = -1;
584  char lc = 0;
585  for (auto i = 0; i < text.size(); i++) {
586  auto c = text.getCharAt(i);
587  if (parseStyle == true) {
588  // end of style
589  if (c == ']') {
590  if (lc != '\\') {
591  auto styleTokenized = StringTools::tokenize(currentStyle, "=");
592  // apply style until current text size
593  if (styleStartIdx != -1 &&
594  (styleFont.empty() == false ||
595  styleColor.empty() == false ||
596  styleUrl.empty() == false)) {
597  if (styleColor.empty() == false) {
598  if (this->text.size() > styleStartIdx) setTextStyle(styleStartIdx, this->text.size() - 1, GUIColor(styleColor), styleFont, styleSize, styleUrl);
599  } else {
600  if (this->text.size() > styleStartIdx) setTextStyle(styleStartIdx, this->text.size() - 1, styleFont, styleSize, styleUrl);
601  }
602  }
603  if (styleTokenized.size() == 2) {
604  auto command = StringTools::toLowerCase(StringTools::trim(styleTokenized[0]));
605  auto argument = StringTools::trim(styleTokenized[1]);
606  if (command == "font") {
607  styleFont = argument;
608  styleStartIdx = this->text.size();
609  } else
610  if (command == "size") {
611  try {
612  styleSize = Integer::parse(argument);
613  } catch (Exception& exception) {
614  Console::println("GUIStyledTextNode::setText(): size: unknown value: " + argument);
615  }
616  styleStartIdx = this->text.size();
617  } else
618  if (command == "color") {
619  styleColor = argument;
620  styleStartIdx = this->text.size();
621  } else
622  if (command == "url") {
623  styleUrl = argument;
624  styleStartIdx = this->text.size();
625  } else
626  if (command == "image") {
627  parseImage = true;
628  auto imageOptions = StringTools::tokenize(styleTokenized[1], ",");
629  for (const auto& imageOption: imageOptions) {
630  auto nameValuePair = StringTools::tokenize(imageOption, ":");
631  if (nameValuePair.size() != 2) {
632  Console::println("GUIStyledTextNode::setText(): unknown image style command option: " + imageOption);
633  } else {
634  auto name = StringTools::trim(nameValuePair[0]);
635  auto value = StringTools::trim(nameValuePair[1]);
636  if (name.empty() == true || value.empty() == true) {
637  Console::println("GUIStyledTextNode::setText(): unknown image style command option: name or value empty");
638  } else
639  if (name == "width") {
640  try {
641  imageWidth = Integer::parse(value);
642  } catch (Exception& exception) {
643  Console::println("GUIStyledTextNode::setText(): unknown image style command option: width: unknown value: " + value);
644  }
645  } else
646  if (name == "height") {
647  try {
648  imageHeight = Integer::parse(value);
649  } catch (Exception& exception) {
650  Console::println("GUIStyledTextNode::setText(): unknown image style command option: height: unknown value: " + value);
651  }
652  } else
653  if (name == "horizontal-scale") {
654  try {
655  if (StringTools::endsWith(value, "%")) {
656  imageHorizontalScale = Float::parse(value.substr(0, value.length() - 1)) / 100.0f;
657  } else {
658  imageHorizontalScale = Float::parse(value);
659  }
660  } catch (Exception& exception) {
661  Console::println("GUIStyledTextNode::setText(): unknown image style command option: horizontal-scale: unknown value: " + value);
662  }
663  } else
664  if (name == "vertical-scale") {
665  try {
666  if (StringTools::endsWith(value, "%")) {
667  imageVerticalScale = Float::parse(value.substr(0, value.length() - 1)) / 100.0f;
668  } else {
669  imageVerticalScale = Float::parse(value);
670  }
671  } catch (Exception& exception) {
672  Console::println("GUIStyledTextNode::setText(): unknown image style command option: vertical-scale: unknown value: " + value);
673  }
674  } else
675  if (name == "effect-color-mul") {
676  try {
677  imageEffectColorMul = GUIColor(value);
678  } catch (Exception& exception) {
679  Console::println("GUIStyledTextNode::setText(): unknown image style command option: effect-color-mul: unknown value: " + value);
680  }
681  } else
682  if (name == "effect-color-add") {
683  try {
684  imageEffectColorAdd = GUIColor(value);
685  } catch (Exception& exception) {
686  Console::println("GUIStyledTextNode::setText(): unknown image style command option: effect-color-add: unknown value: " + value);
687  }
688  } else {
689  Console::println("GUIStyledTextNode::setText(): image style command option: " + name + " = '" + value + "'");
690  }
691  }
692  }
693  } else {
694  Console::println("GUIStyledTextNode::setText(): unknown style command: " + currentStyle);
695  }
696  } else
697  if (styleTokenized.size() == 1) {
698  auto command = StringTools::toLowerCase(StringTools::trim(styleTokenized[0]));
699  if (command == "/font") {
700  styleFont.clear();
701  } else
702  if (command == "/size") {
703  styleSize = size;
704  } else
705  if (command == "/color") {
706  styleColor.clear();
707  } else
708  if (command == "/url") {
709  styleUrl.clear();
710  } else
711  if (command == "image") {
712  parseImage = true;
713  } else
714  if (command == "/image") {
715  parseImage = false;
716  this->text.append(static_cast<char>(0));
717  setImage(this->text.size() - 1, styleImage, styleUrl, imageWidth, imageHeight, imageHorizontalScale, imageVerticalScale, imageEffectColorMul, imageEffectColorAdd);
718  styleImage.clear();
719  imageWidth = -1;
720  imageHeight = -1;
721  imageHorizontalScale = 1.0f;
722  imageVerticalScale = 1.0f;
723  imageEffectColorMul = GUIColor::GUICOLOR_EFFECT_COLOR_MUL;
724  imageEffectColorAdd = GUIColor::GUICOLOR_EFFECT_COLOR_ADD;
725  } else {
726  Console::println("GUIStyledTextNode::setText(): unknown style command: " + currentStyle);
727  }
728  } else {
729  Console::println("GUIStyledTextNode::setText(): unknown style command: " + currentStyle);
730  }
731  //
732  currentStyle.clear();
733  parseStyle = false;
734  if (styleFont.empty() == false ||
735  styleColor.empty() == false ||
736  styleUrl.empty() == false) {
737  styleStartIdx = this->text.size();
738  }
739  } else {
740  this->text.remove(this->text.size() - 1, 1);
741  currentStyle+= c;
742  }
743  } else {
744  // style command
745  currentStyle+= c;
746  }
747  } else
748  // start of style
749  if (c == '[') {
750  if (lc != '\\') {
751  parseStyle = true;
752  } else {
753  this->text.remove(this->text.size() - 1, 1);
754  this->text.append(c);
755  }
756  } else {
757  if (c == ']' && lc == '\\') {
758  this->text.remove(this->text.size() - 1, 1);
759  }
760  if (parseImage == true) {
761  // image
762  styleImage+= c;
763  } else {
764  // ordinary text
765  this->text.append(c);
766  }
767  }
768  //
769  lc = c;
770  }
771  // apply style until current text size
772  if (styleStartIdx != -1 &&
773  (styleFont.empty() == false ||
774  styleColor.empty() == false ||
775  styleUrl.empty() == false)) {
776  if (styleColor.empty() == false) {
777  if (this->text.size() > styleStartIdx) setTextStyle(styleStartIdx, this->text.size() - 1, GUIColor(styleColor), styleFont, styleSize, styleUrl);
778  } else {
779  if (this->text.size() > styleStartIdx) setTextStyle(styleStartIdx, this->text.size() - 1, styleFont, styleSize, styleUrl);
780  }
781  }
782 
783  // add a trailing newline
784  if (StringTools::endsWith(this->text.getString(), newLine) == false) this->text.append(newLine);
785 
786  //
787  this->charEndIdx = text.size();
788 }
789 
791 {
792  unsetStyles();
793  if (font != nullptr) font->dispose();
795 }
796 
797 void GUIStyledTextNode::determineNextLineConstraints(UTF8CharacterIterator& u8It, int charEndIdx, int textStyleIdx) {
798  //
800 
801  // determine line to render
802  if (preformatted == true) {
803  line.clear();
804  lineCharBinaryIdxs.clear();
805  lineCharIdxs.clear();
806  while (u8It.getBinaryPosition() < charEndIdx && u8It.hasNext() == true) {
807  auto k = u8It.getBinaryPosition();
808  auto kc = u8It.getCharacterPosition();
809  auto c = u8It.next();
810  // line finished?
811  if (c == '\n') {
812  auto l1 = line.size();
813  Character::appendToString(line, c);
814  auto l2 = line.size();
815  for (auto l = l1; l < l2; l++) lineCharBinaryIdxs.push_back(k);
816  lineCharIdxs.push_back(kc);
817  break;
818  } else {
819  auto l1 = line.size();
820  Character::appendToString(line, c);
821  auto l2 = line.size();
822  for (auto l = l1; l < l2; l++) lineCharBinaryIdxs.push_back(k);
823  lineCharIdxs.push_back(kc);
824  }
825  }
826  } else {
827  line.clear();
828  lineCharBinaryIdxs.clear();
829  lineCharIdxs.clear();
830  while (u8It.getBinaryPosition() < charEndIdx && u8It.hasNext() == true) {
831  auto k = u8It.getBinaryPosition();
832  auto kc = u8It.getCharacterPosition();
833  auto c = u8It.next();
834  // line finished?
835  if (c == '\n') {
836  auto l1 = line.size();
837  Character::appendToString(line, c);
838  auto l2 = line.size();
839  for (auto l = l1; l < l2; l++) lineCharBinaryIdxs.push_back(k);
840  lineCharIdxs.push_back(kc);
841  break;
842  } else
843  if (line.empty() == false && c == ' ' && StringTools::endsWith(line, spaceString) == true) {
844  // no op as we have a line which already has a space at the end
845  } else
846  if (line.empty() == true && (c == ' ' || c == '\t')) {
847  // no op
848  } else {
849  auto l1 = line.size();
850  Character::appendToString(line, c);
851  auto l2 = line.size();
852  for (auto l = l1; l < l2; l++) lineCharBinaryIdxs.push_back(k);
853  lineCharIdxs.push_back(kc);
854  }
855  }
856  }
857 
858  // remove trailing space
859  while (StringTools::endsWith(line, spaceString) == true) line.erase(line.begin() + line.size() - 1);
860 
861  if (line.empty() == true) {
862  //
863  lineConstraints.emplace_back(
864  0,
865  0,
866  0.0f,
867  font->getLineHeight(),
868  font->getLineHeight(),
869  font->getBaseLine(),
870  true
871  );
872  } else {
873  // determine baseline and part of line to render
874  auto baseLine = 0.0f;
875  auto lineHeight = 0.0f;
876  auto lineWidth = 0.0f;
877  auto lineWidthSpaceWrap = 0.0f;
878  auto lineHeightSpaceWrap = 0.0f;
879  auto baseLineSpaceWrap = 0.0f;
880  auto imageHeight = 0.0f;
881  auto charIdx = 0;
882 
883  //
884  lineConstraints.clear();
885  lineConstraints.emplace_back(
886  -1,
887  -1,
888  0.0f,
889  0.0f,
890  0.0f,
891  0.0f,
892  false
893  );
894  {
895  auto currentTextStyleIdx = textStyleIdx;
896  UTF8CharacterIterator lineU8It(line);
897  while (lineU8It.hasNext() == true) {
898  charIdx = lineU8It.getCharacterPosition();
899  auto k = lineU8It.getBinaryPosition();
900  auto kc = charIdx;
901  auto c = lineU8It.next();
902  auto textStyle = getTextStyle(lineCharBinaryIdxs, k, currentTextStyleIdx);
903  auto currentFont = textStyle != nullptr && textStyle->font != nullptr?textStyle->font:font;
904  baseLine = Math::max(baseLine, currentFont->getBaseLine());
905  baseLineSpaceWrap = Math::max(baseLineSpaceWrap, currentFont->getBaseLine());
906  lineHeight = Math::max(lineHeight, currentFont->getLineHeight());
907  lineHeightSpaceWrap = Math::max(lineHeightSpaceWrap, currentFont->getLineHeight());
908  // render a image
909  if (textStyle != nullptr && textStyle->image != nullptr) {
910  if (lineConstraints[lineConstraints.size() - 1].spaceWrap == false) {
911  lineConstraints[lineConstraints.size() - 1] = Line(
912  k,
913  kc,
914  lineWidth,
915  Math::max(lineHeight, baseLine + imageHeight),
916  lineHeight,
917  baseLine,
918  false
919  );
920  lineWidthSpaceWrap = 0.0f;
921  lineHeightSpaceWrap = 0.0f;
922  baseLineSpaceWrap = 0.0f;
923  }
924  if (lineWidth > maxLineWidth) {
925  imageHeight = 0.0f;
926  lineWidth = lineWidthSpaceWrap;
927  lineHeight = lineHeightSpaceWrap;
928  baseLine = baseLineSpaceWrap;
929  lineConstraints.emplace_back(
930  -1,
931  kc,
932  0.0f,
933  0.0f,
934  0.0f,
935  0.0f,
936  false
937  );
938  }
939  lineWidth+= textStyle->width;
940  lineWidthSpaceWrap+= textStyle->width;
941  imageHeight = Math::max(imageHeight, static_cast<float>(textStyle->height));
942  } else {
943  // render text
944  if (c == ' ') {
945  lineConstraints[lineConstraints.size() - 1] = Line(
946  k,
947  kc,
948  lineWidth,
949  Math::max(lineHeight, baseLine + imageHeight),
950  lineHeight,
951  baseLine,
952  true
953  );
954  lineWidthSpaceWrap = 0.0f;
955  lineHeightSpaceWrap = 0.0f;
956  baseLineSpaceWrap = 0.0f;
957  }
958  auto character = currentFont->getCharacter(c == '\t'?' ':c);
959  if (character != nullptr) {
960  if (lineConstraints[lineConstraints.size() - 1].spaceWrap == false) {
961  lineConstraints[lineConstraints.size() - 1] = Line(
962  k,
963  kc,
964  lineWidth,
965  Math::max(lineHeight, baseLine + imageHeight),
966  lineHeight,
967  baseLine,
968  false
969  );
970  lineWidthSpaceWrap = 0.0f;
971  lineHeightSpaceWrap = 0.0f;
972  baseLineSpaceWrap = 0.0f;
973  }
974  if (lineWidth > maxLineWidth) {
975  lineWidth = lineWidthSpaceWrap;
976  if (lineU8It.hasNext() == true) {
977  imageHeight = 0.0f;
978  lineHeight = lineHeightSpaceWrap;
979  baseLine = baseLineSpaceWrap;
980  }
981  lineConstraints.emplace_back(
982  -1,
983  -1,
984  0.0f,
985  0.0f,
986  0.0f,
987  0.0f,
988  false
989  );
990  }
991  auto charXAdvance = c == '\t'?character->getXAdvance() * tabSize:character->getXAdvance();
992  lineWidth+= charXAdvance;
993  lineWidthSpaceWrap+= lineWidthSpaceWrap < Math::EPSILON && (c == ' ' || c == '\t')?0.0f:charXAdvance;
994  }
995  }
996  }
997  }
998 
999  //
1000  lineConstraints[lineConstraints.size() - 1] = Line(
1001  static_cast<int>(line.size()),
1002  charIdx,
1003  lineWidth,
1004  Math::max(lineHeight, baseLine + imageHeight),
1005  lineHeight,
1006  baseLine,
1007  false
1008  );
1009  }
1010 }
1011 
1013 {
1014  if (shouldRender() == false) return;
1015 
1016  //
1017  GUINode::render(guiRenderer);
1018 
1019  //
1020  if (font == nullptr) return;
1021 
1022  // screen dimensions
1023  auto screenWidth = screenNode->getScreenWidth();
1024  auto screenHeight = screenNode->getScreenHeight();
1025 
1026  // indents
1029 
1030  // vertical alignment
1031  auto y = 0;
1033  // no op
1034  } else
1037  } else
1040  }
1041 
1042  //
1043  auto parentXOffset = computeParentChildrenRenderOffsetXTotal();
1044  auto parentYOffset = computeParentChildrenRenderOffsetYTotal();
1045  bool visible = false;
1046 
1047  // did a scrolling appear, then reset bounds to work with
1048  if (parentOffsetsChanged == true ||
1049  Float::equals(parentXOffset, parentXOffsetLast) == true ||
1050  Float::equals(parentYOffset, parentYOffsetLast) == true) {
1051  parentXOffsetLast = parentXOffset;
1052  parentYOffsetLast = parentYOffset;
1053  charStartIdx = 0;
1054  charEndIdx = text.size();
1055  startTextStyleIdx = -1;
1056  parentOffsetsChanged = false;
1057  startRenderY = 0;
1058  } else {
1059  y = startRenderY;
1060  }
1061 
1062  // Console::println("char start idx: " + to_string(charStartIdx) + ", char end idx: " + to_string(charEndIdx) + ", chars: " + to_string(text.size()) + ", start text style idx: " + to_string(startTextStyleIdx) + ", start render y: " + to_string(startRenderY) + ", auto width: " + to_string(autoWidth) + ", auto height = " + to_string(autoHeight))
1063 
1064  //
1065  auto styledTextController = required_dynamic_cast<GUIStyledTextNodeController*>(getController());
1067  auto cursorIndex = styledTextController->getIndex();
1068  auto cursorSelectionIndex = styledTextController->getSelectionIndex();
1069  if (editMode == true) {
1070  cursorMode = styledTextController->getCursorMode();
1071  }
1072 
1073  //
1074  urlAreas.clear();
1075 
1076  // do we have a mouse x and y index position
1080  if (findNewIndex == true) {
1081  cursorIndex = -1;
1084  indexMousePositionX+= xIndentLeft;
1085  indexMousePositionY+= yIndentTop;
1086  }
1087 
1088  // do we have a mouse x and y selection index position
1091  auto findNewSelectionIndex = editMode == true && findNewIndex == false && selectionIndexMousePositionX != MOUSEPOSITION_NONE && selectionIndexMousePositionY != MOUSEPOSITION_NONE;
1092  if (findNewSelectionIndex == true) {
1093  cursorSelectionIndex = -1;
1096  selectionIndexMousePositionX+= xIndentLeft;
1097  selectionIndexMousePositionY+= yIndentTop;
1098  }
1099 
1100  //
1101  auto maxLineWidth = getAutoWidth();
1102  auto textStyleIdx = startTextStyleIdx;
1103  auto boundTexture = -1;
1104  GUIColor lastColor = color;
1105  string currentURL;
1106  string styleURL;
1107  int x = 0;
1108  auto u8It = text.getUTF8CharacterIterator();
1110  for (;u8It.hasNext() == true && u8It.getBinaryPosition() < charEndIdx;) {
1111 
1112  //
1113  auto nextCharStartIdx = u8It.getBinaryPosition();
1114  auto nextStartTextStyleIdx = textStyleIdx;
1115  auto nextStartRenderY = y;
1116 
1117  //
1118  determineNextLineConstraints(u8It, charEndIdx, textStyleIdx);
1119 
1120  //
1121  /*
1122  {
1123  auto l = 0;
1124  for (auto k = 0; k < lineConstraints.size(); k++) {
1125  string linePart;
1126  for (auto j = l; j < lineConstraints[k].idx; j++) {
1127  if (line[j] == 0) {
1128  linePart += "[image]";
1129  } else {
1130  linePart += line[j];
1131  }
1132  }
1133  Console::println(
1134  "render line@" + to_string(k) + ": '" + linePart
1135  + "': " + to_string(lineConstraints[k].idx) + "; width = "
1136  + to_string(lineConstraints[k].width) + " / "
1137  + to_string(maxLineWidth) + ", line height = "
1138  + to_string(lineConstraints[k].lineHeight) + ", height "
1139  + to_string(lineConstraints[k].height) + ", base line: "
1140  + to_string(lineConstraints[k].baseLine) + ": Y = " + to_string(y));
1141  l = lineConstraints[k].idx;
1142  }
1143  }
1144  */
1145 
1146  // render
1147  auto lineIdx = 0;
1148  {
1149  //
1150  auto skipSpaces = false;
1151  auto& currentTextStyleIdx = textStyleIdx;
1152  // determine visibility of (sub) lines
1153  for (lineIdx = 0; lineIdx < lineConstraints.size(); lineIdx++) {
1154  // x alignment
1156  x = 0;
1157  } else
1159  x = (maxLineWidth - lineConstraints[lineIdx].width) / 2;
1160  } else
1162  x = maxLineWidth - lineConstraints[lineIdx].width;
1163  }
1164  //
1165  float left = x + xIndentLeft;
1166  float top = y + yIndentTop;
1167  float width = lineConstraints[lineIdx].width;
1168  float height = lineConstraints[lineIdx].height;
1169  if (guiRenderer->isQuadVisible2(
1170  ((left) / (screenWidth / 2.0f)) - 1.0f,
1171  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1172  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1173  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1174  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1175  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1176  ((left) / (screenWidth / 2.0f)) - 1.0f, ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f) == true) {
1177  // break if visible
1178  break;
1179  }
1180  // increment y by line height
1181  y+= lineConstraints[lineIdx].height;
1182  // iterate text style
1183  for (auto k = lineIdx == 0?0:lineConstraints[lineIdx - 1].binaryIdx; k < lineConstraints[lineIdx].binaryIdx; k++)
1184  getTextStyle(lineCharBinaryIdxs, k, currentTextStyleIdx);
1185  }
1186  // render
1187  if (lineIdx == lineConstraints.size()) {
1188  // was visible, then store text render end values
1189  if (visible == true) {
1190  visible = false;
1192  break;
1193  }
1194  } else {
1195  // if text was not visible before store text render start values
1196  if (visible == false) {
1197  visible = true;
1198  //
1199  charStartIdx = nextCharStartIdx;
1200  startTextStyleIdx = nextStartTextStyleIdx;
1201  startRenderY = nextStartRenderY;
1202  }
1203  // x alignment
1205  // no op
1206  x = 0;
1207  } else
1209  x = (maxLineWidth - lineConstraints[lineIdx].width) / 2;
1210  } else
1212  x = maxLineWidth - lineConstraints[lineIdx].width;
1213  }
1214  // find new indices if requested
1215  // index
1216  if (findNewIndex == true &&
1217  indexMousePositionY >= y + yIndentTop && indexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1218  indexMousePositionX <= x + xIndentLeft) {
1219  cursorIndex = lineCharIdxs[0];
1220  }
1221  // selection index
1222  if (findNewSelectionIndex == true &&
1223  selectionIndexMousePositionY >= y + yIndentTop && selectionIndexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1224  selectionIndexMousePositionX <= x + xIndentLeft) {
1225  cursorSelectionIndex = lineCharIdxs[0];
1226  }
1227  // render
1228  UTF8CharacterIterator lineU8It(line);
1229  lineU8It.seekBinaryPosition(lineIdx == 0?0:lineConstraints[lineIdx - 1].binaryIdx);
1230  while (lineU8It.hasNext() == true) {
1231  auto k = lineU8It.getBinaryPosition();
1232  auto kc = lineU8It.getCharacterPosition();
1233  auto c = lineU8It.next();
1234  auto textStyle = getTextStyle(lineCharBinaryIdxs, k, currentTextStyleIdx);
1235  Color4 currentColor = color;
1236  GUIFont* currentFont = font;
1237  styleURL.clear();
1238  // apply text style or defaults
1239  if (textStyle != nullptr) {
1240  currentFont = textStyle->font != nullptr?textStyle->font:font;
1241  currentColor = textStyle->color;
1242  styleURL = textStyle->url;
1243  }
1244  // image rendering
1245  if (textStyle != nullptr && textStyle->image != nullptr) {
1246  // draw
1247  guiRenderer->render();
1248  auto rendererEffectColorMul = guiRenderer->getGUIEffectColorMul();
1249  auto rendererEffectColorAdd = guiRenderer->getGUIEffectColorAdd();
1250  guiRenderer->setGUIEffectColorMul(textStyle->effectColorMul);
1251  guiRenderer->setGUIEffectColorAdd(textStyle->effectColorAdd);
1252  guiRenderer->bindTexture(textStyle->textureId);
1253  {
1254  float left = x + xIndentLeft;
1255  float top = y + yIndentTop + (lineConstraints[lineIdx].baseLine - textStyle->height) + (lineConstraints[lineIdx].height - lineConstraints[lineIdx].lineHeight);
1256  float width = textStyle->width;
1257  float height = textStyle->height;
1258  guiRenderer->addQuad(
1259  ((left) / (screenWidth / 2.0f)) - 1.0f,
1260  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1261  1.0f, 1.0f, 1.0f, 1.0f,
1262  0.0f,
1263  0.0f,
1264  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1265  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1266  1.0f, 1.0f, 1.0f, 1.0f,
1267  1.0f,
1268  0.0f,
1269  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1270  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1271  1.0f, 1.0f, 1.0f, 1.0f,
1272  1.0f,
1273  1.0f,
1274  ((left) / (screenWidth / 2.0f)) - 1.0f,
1275  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1276  1.0f, 1.0f, 1.0f, 1.0f,
1277  0.0f,
1278  1.0f
1279  );
1280  }
1281  if (cursorMode == GUIStyledTextNodeController::CURSORMODE_SHOW && (findNewSelectionIndex == true?cursorSelectionIndex == lineCharIdxs[kc]:cursorIndex == lineCharIdxs[kc])) {
1282  // draw cursor
1283  float left = x + xIndentLeft;
1284  float top = y + yIndentTop + (lineConstraints[lineIdx].baseLine - textStyle->height) + (lineConstraints[lineIdx].height - lineConstraints[lineIdx].lineHeight);
1285  float width = 2;
1286  float height = textStyle->height;
1287  guiRenderer->addQuad(
1288  ((left) / (screenWidth / 2.0f)) - 1.0f,
1289  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1290  color[0], color[1], color[2], color[3],
1291  0.0f, 1.0f,
1292  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1293  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1294  color[0], color[1], color[2], color[3],
1295  1.0f, 1.0f,
1296  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1297  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1298  color[0], color[1], color[2], color[3],
1299  1.0f, 0.0f,
1300  ((left) / (screenWidth / 2.0f)) - 1.0f,
1301  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1302  color[0], color[1], color[2], color[3],
1303  0.0f, 0.0f,
1304  true
1305  );
1306  // store new index position
1307  indexPositionX = static_cast<int>(left);
1308  indexPositionY = static_cast<int>(top);
1309  }
1310  //
1311  guiRenderer->render();
1312  guiRenderer->bindTexture(boundTexture);
1313  guiRenderer->setGUIEffectColorMul(rendererEffectColorMul);
1314  guiRenderer->setGUIEffectColorAdd(rendererEffectColorAdd);
1315  // flush current URL
1316  if (currentURL.empty() == false && urlAreas.empty() == false) {
1317  auto& urlArea = urlAreas[urlAreas.size() - 1];
1318  urlArea.width = x - urlArea.left;
1319  urlArea.height = lineConstraints[lineIdx].lineHeight;
1320  }
1321  currentURL.clear();
1322  // add url area if URL is given
1323  if (textStyle->url.empty() == false) {
1324  urlAreas.emplace_back(
1325  static_cast<int>(x),
1326  static_cast<int>(y),
1327  static_cast<int>(textStyle->width),
1328  static_cast<int>(textStyle->height),
1329  textStyle->url
1330  );
1331  }
1332  //
1333  x+= textStyle->width;
1334  } else {
1335  // text rendering
1336  // do line break
1337  if (k == lineConstraints[lineIdx].binaryIdx) {
1338  // flush current URL
1339  if (currentURL.empty() == false && urlAreas.empty() == false) {
1340  auto& urlArea = urlAreas[urlAreas.size() - 1];
1341  urlArea.width = x - urlArea.left;
1342  urlArea.height = lineConstraints[lineIdx].lineHeight;
1343  }
1344  //
1345  currentURL.clear();
1346  //
1347  y+= lineConstraints[lineIdx].height;
1348  lineIdx++;
1349  if (lineIdx == lineConstraints.size()) break;
1350  //
1351  x = 0;
1353  // no op
1354  } else
1356  x = (maxLineWidth - lineConstraints[lineIdx].width) / 2;
1357  } else
1359  x = maxLineWidth - lineConstraints[lineIdx].width;
1360  }
1361  if (lineConstraints[lineIdx - 1].spaceWrap == true) {
1362  skipSpaces = true;
1363  }
1364  // find new indices if requested
1365  // index
1366  if (findNewIndex == true &&
1367  indexMousePositionY >= y + yIndentTop && indexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1368  indexMousePositionX <= x + xIndentLeft) {
1369  cursorIndex = lineCharIdxs[kc];
1370  }
1371  // selection index
1372  if (findNewSelectionIndex == true &&
1373  selectionIndexMousePositionY >= y + yIndentTop && selectionIndexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1374  selectionIndexMousePositionX <= x + xIndentLeft) {
1375  cursorSelectionIndex = lineCharIdxs[kc];
1376  }
1377  //
1378  {
1379  float left = x + xIndentLeft;
1380  float top = y + yIndentTop;
1381  float width = lineConstraints[lineIdx].width;
1382  float height = lineConstraints[lineIdx].height;
1383  if (guiRenderer->isQuadVisible2(
1384  ((left) / (screenWidth / 2.0f)) - 1.0f,
1385  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1386  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1387  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1388  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1389  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1390  ((left) / (screenWidth / 2.0f)) - 1.0f, ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f) == false) {
1391  //
1392  if (visible == true) {
1393  visible = false;
1394  //
1395 
1396  charEndIdx = lineCharBinaryIdxs[lineIdx == 0?0:lineConstraints[lineIdx - 1].charIdx];
1397  break;
1398  }
1399  }
1400  }
1401  }
1402  // skip spaces if requested
1403  if (skipSpaces == true) {
1404  if (c == ' ' || c == '\t') {
1405  continue;
1406  } else {
1407  skipSpaces = false;
1408  }
1409  }
1410  if (c == '\n') {
1411  // find new indices if requested
1412  // index
1413  if (findNewIndex == true &&
1414  indexMousePositionY >= y + yIndentTop && indexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1415  indexMousePositionX >= x + xIndentLeft) {
1416  cursorIndex = lineCharIdxs[kc];
1417  }
1418  // selection index
1419  if (findNewSelectionIndex == true &&
1420  selectionIndexMousePositionY >= y + yIndentTop && selectionIndexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1421  selectionIndexMousePositionX >= x + xIndentLeft) {
1422  cursorSelectionIndex = lineCharIdxs[kc];
1423  }
1424  // draw cursor
1425  if (cursorMode == GUIStyledTextNodeController::CURSORMODE_SHOW && (findNewSelectionIndex == true?cursorSelectionIndex == lineCharIdxs[kc]:cursorIndex == lineCharIdxs[kc])) {
1426  float left = x + xIndentLeft;
1427  float top = y + yIndentTop + (lineConstraints[lineIdx].baseLine - currentFont->getBaseLine()) + (lineConstraints[lineIdx].height - lineConstraints[lineIdx].lineHeight);
1428  float width = 2;
1429  float height = lineConstraints[lineIdx].lineHeight;
1430  guiRenderer->addQuad(
1431  ((left) / (screenWidth / 2.0f)) - 1.0f,
1432  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1433  color[0], color[1], color[2], color[3],
1434  0.0f, 1.0f,
1435  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1436  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1437  color[0], color[1], color[2], color[3],
1438  1.0f, 1.0f,
1439  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1440  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1441  color[0], color[1], color[2], color[3],
1442  1.0f, 0.0f,
1443  ((left) / (screenWidth / 2.0f)) - 1.0f,
1444  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1445  color[0], color[1], color[2], color[3],
1446  0.0f, 0.0f,
1447  true
1448  );
1449  // store new index position
1450  indexPositionX = static_cast<int>(left);
1451  indexPositionY = static_cast<int>(top);
1452  }
1453  } else {
1454  // otherwise draw
1455  auto characterCount = c == '\t'?tabSize:1;
1456  auto character = currentFont->getCharacter(c == '\t'?' ':c);
1457  if (character != nullptr) {
1458  // next x advance
1459  auto xAdvance = c == '\t'?tabSize * character->getXAdvance():character->getXAdvance();
1460 
1461  // find new indices if requested
1462  // index
1463  if (findNewIndex == true &&
1464  indexMousePositionY >= y + yIndentTop && indexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1465  indexMousePositionX >= x + xIndentLeft && indexMousePositionX < x + xIndentLeft + xAdvance) {
1466  cursorIndex = lineCharIdxs[kc];
1467  }
1468  // selection index
1469  if (findNewSelectionIndex == true &&
1470  selectionIndexMousePositionY >= y + yIndentTop && selectionIndexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1471  selectionIndexMousePositionX >= x + xIndentLeft && selectionIndexMousePositionX < x + xIndentLeft + xAdvance) {
1472  cursorSelectionIndex = lineCharIdxs[kc];
1473  }
1474 
1475  //
1476  auto hasSelection = false;
1477  if (cursorSelectionIndex != -1 || findNewSelectionIndex == true) {
1478  if ((cursorSelectionIndex != -1 && lineCharIdxs[kc] >= Math::min(cursorIndex, cursorSelectionIndex) && lineCharIdxs[kc] < Math::max(cursorIndex, cursorSelectionIndex)) ||
1479  (cursorSelectionIndex == -1 && lineCharIdxs[kc] >= cursorIndex)) {
1480  for (auto l = 0; l < characterCount; l++) {
1481  float left = x + xIndentLeft + (l * character->getXAdvance());
1482  float top = y + yIndentTop + (lineConstraints[lineIdx].baseLine - currentFont->getBaseLine()) + (lineConstraints[lineIdx].height - lineConstraints[lineIdx].lineHeight);
1483  currentFont->drawCharacterBackground(guiRenderer, character, left, top, lineConstraints[lineIdx].lineHeight, selectionBackgroundColor);
1484  }
1485  hasSelection = true;
1486  }
1487  }
1488  // draw character
1489  for (auto l = 0; l < characterCount; l++) {
1490  float left = x + xIndentLeft + (l * character->getXAdvance());
1491  float top = y + yIndentTop + (lineConstraints[lineIdx].baseLine - currentFont->getBaseLine()) + (lineConstraints[lineIdx].height - lineConstraints[lineIdx].lineHeight);
1492  if (boundTexture == -1) {
1493  boundTexture = currentFont->getTextureId();
1494  guiRenderer->bindTexture(boundTexture);
1495  lastColor = currentColor;
1496  } else
1497  if (boundTexture != currentFont->getTextureId()) {
1498  boundTexture = currentFont->getTextureId();
1499  guiRenderer->render();
1500  guiRenderer->bindTexture(boundTexture);
1501  lastColor = currentColor;
1502  } else
1503  if (currentColor.equals(lastColor) == false) {
1504  guiRenderer->render();
1505  lastColor = currentColor;
1506  }
1507  currentFont->drawCharacter(guiRenderer, character, left, top, currentColor);
1508  }
1509 
1510  // draw cursor
1511  if (cursorMode == GUIStyledTextNodeController::CURSORMODE_SHOW && (findNewSelectionIndex == true?cursorSelectionIndex == lineCharIdxs[kc]:cursorIndex == lineCharIdxs[kc])) {
1512  float left = x + xIndentLeft;
1513  float top = y + yIndentTop + (lineConstraints[lineIdx].baseLine - currentFont->getBaseLine()) + (lineConstraints[lineIdx].height - lineConstraints[lineIdx].lineHeight);
1514  float width = 2;
1515  float height = lineConstraints[lineIdx].lineHeight;
1516  guiRenderer->addQuad(
1517  ((left) / (screenWidth / 2.0f)) - 1.0f,
1518  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1519  color[0], color[1], color[2], color[3],
1520  0.0f, 1.0f,
1521  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1522  ((screenHeight - top) / (screenHeight / 2.0f)) - 1.0f,
1523  color[0], color[1], color[2], color[3],
1524  1.0f, 1.0f,
1525  ((left + width) / (screenWidth / 2.0f)) - 1.0f,
1526  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1527  color[0], color[1], color[2], color[3],
1528  1.0f, 0.0f,
1529  ((left) / (screenWidth / 2.0f)) - 1.0f,
1530  ((screenHeight - top - height) / (screenHeight / 2.0f)) - 1.0f,
1531  color[0], color[1], color[2], color[3],
1532  0.0f, 0.0f,
1533  true
1534  );
1535  // store new index position
1536  indexPositionX = static_cast<int>(left);
1537  indexPositionY = static_cast<int>(top);
1538  }
1539 
1540  // if URL did change, create URL areas
1541  if (styleURL != currentURL) {
1542  if (currentURL.empty() == false && urlAreas.empty() == false) {
1543  auto& urlArea = urlAreas[urlAreas.size() - 1];
1544  urlArea.width = x - urlArea.left;
1545  urlArea.height = lineConstraints[lineIdx].lineHeight;
1546  }
1547  if (styleURL.empty() == false) {
1548  urlAreas.emplace_back(
1549  static_cast<int>(x),
1550  static_cast<int>(y),
1551  -1,
1552  1,
1553  styleURL
1554  );
1555  }
1556  currentURL = styleURL;
1557  }
1558 
1559  // advance X
1560  x+= xAdvance;
1561  }
1562  }
1563  }
1564  }
1565 
1566  // find new indices if requested
1567  // index
1568  if (findNewIndex == true &&
1569  indexMousePositionY >= y + yIndentTop && indexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1570  indexMousePositionX >= x + xIndentLeft) {
1571  cursorIndex = lineCharIdxs[lineCharIdxs.size() - 1];
1572  }
1573 
1574  // selection index
1575  if (findNewSelectionIndex == true &&
1576  selectionIndexMousePositionY >= y + yIndentTop && selectionIndexMousePositionY < y + yIndentTop + lineConstraints[lineIdx].height &&
1577  selectionIndexMousePositionX >= x + xIndentLeft) {
1578  cursorSelectionIndex = lineCharIdxs[lineCharIdxs.size() - 1];
1579  }
1580 
1581  //
1582  if (lineConstraints[lineConstraints.size() - 1].binaryIdx == line.size()) {
1583  y+= lineConstraints[lineConstraints.size() - 1].height;
1584  }
1585 
1586  // flush current URL
1587  if (currentURL.empty() == false && urlAreas.empty() == false) {
1588  auto& urlArea = urlAreas[urlAreas.size() - 1];
1589  urlArea.width = x - urlArea.left;
1590  urlArea.height = lineConstraints[lineIdx].lineHeight;
1591  }
1592  currentURL.clear();
1593  }
1594  }
1595 
1596  //
1597  line.clear();
1598  lineCharBinaryIdxs.clear();
1599  lineConstraints.clear();
1600  }
1601 
1602  //
1603  // Console::println("y: " + to_string(y) + " / " + to_string(autoHeight));
1604 
1605  //
1606  guiRenderer->render();
1607  guiRenderer->bindTexture(0);
1608 
1609  // index
1610  if (findNewIndex == true) {
1611  if (cursorIndex != -1) styledTextController->setIndex(cursorIndex);
1613  }
1614  // selection index
1615  if (findNewSelectionIndex == true) {
1616  if (cursorSelectionIndex != -1) styledTextController->setSelectionIndex(cursorSelectionIndex);
1617  }
1618 }
1619 
1621  for (const auto& style: styles) {
1622  if (style.font != nullptr) font->dispose();
1623  if (style.image != nullptr) Engine::getInstance()->getTextureManager()->removeTexture(style.image->getId());
1624  }
1625  styles.clear();
1626  startTextStyleIdx = -1;
1627 }
1628 
1629 void GUIStyledTextNode::unsetTextStyle(int startIdx, int endIdx) {
1630  // Console::print("GUIStyledTextNode::unsetTextStyle(): " + to_string(startIdx) + " ... " + to_string(endIdx) + ": '");
1631  // for (auto i = startIdx; i <= endIdx; i++) Console::print(string() + text.charAt(i));
1632  // Console::println("'");
1633  // for (const auto& style: styles) Console::println("pre: " + to_string(style.startIdx) + " ... " + to_string(style.endIdx));
1634  for (auto i = 0; i < styles.size(); i++) {
1635  auto& style = styles[i];
1636  if (startIdx >= style.startIdx && endIdx <= style.endIdx) {
1637  auto styleNext = style;
1638  style.endIdx = startIdx - 1;
1639  styleNext.startIdx = endIdx + 1;
1640  styles.insert(styles.begin() + i + 1, styleNext);
1641  i++;
1642  continue;
1643  } else {
1644  if (startIdx >= style.startIdx && startIdx <= style.endIdx) {
1645  style.endIdx = startIdx - 1;
1646  }
1647  if (endIdx >= style.startIdx && endIdx <= style.endIdx) {
1648  style.startIdx = endIdx - 1;
1649  }
1650  }
1651  if (style.startIdx > style.endIdx ||
1652  (startIdx < style.startIdx && endIdx > style.endIdx)) {
1653  styles.erase(styles.begin() + i);
1654  i--;
1655  }
1656  }
1657  // for (const auto& style: styles) Console::println("post: " + to_string(style.startIdx) + " ... " + to_string(style.endIdx));
1658 }
1659 
1660 void GUIStyledTextNode::setTextStyle(int startIdx, int endIdx, const GUIColor& color, const string& font, int size, const string& url) {
1661  // Console::print("GUIStyledTextNode::setTextStyle(): " + to_string(startIdx) + " ... " + to_string(endIdx) + ": '");
1662  // for (auto i = startIdx; i <= endIdx; i++) Console::print(string() + text.charAt(i));
1663  // Console::print("'");
1664  // Console::print(", color = " + to_string(color.getRed()) + ", " + to_string(color.getGreen()) + ", " + to_string(color.getBlue()) + ", " + to_string(color.getAlpha()));
1665  // Console::print(", url = '" + url + "'");
1666  // Console::println();
1667  unsetTextStyle(startIdx, endIdx);
1668  //
1669  if (size <= 0) size = this->size;
1670  auto _font = font.empty() == true?nullptr:screenNode->getFont(font, size);
1671  if (_font != nullptr) _font->initialize();
1672  // find position to insert
1673  auto j = -1;
1674  for (auto i = 0; i < styles.size(); i++) {
1675  const auto& style = styles[i];
1676  if (startIdx > style.endIdx) {
1677  j = i + 1;
1678  }
1679  }
1680  // Console::println("insert@" + to_string(j));
1681  if (j == -1) j = 0;
1682  // for (const auto& style: styles) Console::println("pre: " + to_string(style.startIdx) + " ... " + to_string(style.endIdx));
1683  // insert
1684  styles.insert(
1685  styles.begin() + j,
1686  {
1687  .startIdx = startIdx,
1688  .endIdx = endIdx,
1689  .color = color,
1690  .font = _font,
1691  .url = url,
1692  .image = nullptr,
1693  .textureId = -1,
1694  .width = -1,
1695  .height = -1,
1696  .effectColorMul = GUIColor::GUICOLOR_EFFECT_COLOR_MUL,
1697  .effectColorAdd = GUIColor::GUICOLOR_EFFECT_COLOR_ADD
1698  }
1699  );
1700  //
1701  startTextStyleIdx = -1;
1702  // for (const auto& style: styles) Console::println("post: " + to_string(style.startIdx) + " ... " + to_string(style.endIdx));
1703 }
1704 
1705 void GUIStyledTextNode::setTextStyle(int startIdx, int endIdx, const string& font, int size, const string& url) {
1706  // Console::print("GUIStyledTextNode::setTextStyle(): " + to_string(startIdx) + " ... " + to_string(endIdx) + ": '");
1707  // for (auto i = startIdx; i <= endIdx; i++) Console::print(string() + text.charAt(i));
1708  // Console::print("'");
1709  // Console::print(", url = '" + url + "'");
1710  // Console::println();
1711  unsetTextStyle(startIdx, endIdx);
1712  //
1713  if (size <= 0) size = this->size;
1714  auto _font = font.empty() == true?nullptr:screenNode->getFont(font, size);
1715  if (_font != nullptr) _font->initialize();
1716  // find position to insert
1717  auto j = -1;
1718  for (auto i = 0; i < styles.size(); i++) {
1719  const auto& style = styles[i];
1720  if (startIdx > style.endIdx) {
1721  j = i + 1;
1722  }
1723  }
1724  // for (const auto& style: styles) Console::println("pre: " + to_string(style.startIdx) + " ... " + to_string(style.endIdx));
1725  // Console::println("insert@" + to_string(j));
1726  if (j == -1) j = 0;
1727  // insert
1728  styles.insert(
1729  styles.begin() + j,
1730  {
1731  .startIdx = startIdx,
1732  .endIdx = endIdx,
1733  .color = color,
1734  .font = _font,
1735  .url = url,
1736  .image = nullptr,
1737  .textureId = -1,
1738  .width = -1,
1739  .height = -1,
1740  .effectColorMul = GUIColor::GUICOLOR_EFFECT_COLOR_MUL,
1741  .effectColorAdd = GUIColor::GUICOLOR_EFFECT_COLOR_ADD
1742  }
1743  );
1744  //
1745  startTextStyleIdx = -1;
1746  // for (const auto& style: styles) Console::println("post: " + to_string(style.startIdx) + " ... " + to_string(style.endIdx));
1747 }
1748 
1749 void GUIStyledTextNode::setImage(int idx, const string& image, const string& url, int width, int height, float horizontalScale, float verticalScale, const GUIColor& effectColorMul, const GUIColor& effectColorAdd) {
1750  // Console::println("GUIStyledTextNode::setImage(): " + to_string(idx) + ": " + image + ", url = '" + url + "', width = " + to_string(width) + ", height = " + to_string(height));
1751  unsetTextStyle(idx,idx);
1752  // TODO: a.drewke
1753  auto _image = image.empty() == true?nullptr:screenNode->getImage(image);
1754  // find position to insert
1755  auto j = -1;
1756  for (auto i = 0; i < styles.size(); i++) {
1757  const auto& style = styles[i];
1758  if (idx > style.endIdx) {
1759  j = i + 1;
1760  }
1761  }
1762  // for (const auto& style: styles) Console::println("pre: " + to_string(style.startIdx) + " ... " + to_string(style.endIdx));
1763  // Console::println("insert@" + to_string(j));
1764  if (j == -1) j = 0;
1765  // insert
1766  styles.insert(
1767  styles.begin() + j,
1768  {
1769  .startIdx = idx,
1770  .endIdx = idx,
1771  .color = color,
1772  .font = nullptr,
1773  .url = url,
1774  .image = _image,
1775  .textureId = Engine::getInstance()->getTextureManager()->addTexture(_image, 0),
1776  .width = width == -1?static_cast<int>(_image->getWidth() * horizontalScale):static_cast<int>(width * horizontalScale),
1777  .height = height == -1?static_cast<int>(_image->getHeight() * verticalScale):static_cast<int>(height * verticalScale),
1778  .effectColorMul = effectColorMul,
1779  .effectColorAdd = effectColorAdd
1780  }
1781  );
1782  //
1783  startTextStyleIdx = -1;
1784  // for (const auto& style: styles) Console::println("post: " + to_string(style.startIdx) + " ... " + to_string(style.endIdx));
1785 }
Color 4 definition class.
Definition: Color4.h:18
bool equals(const Color4 &color, float tolerance=Math::EPSILON) const
Compares this color with given color.
Definition: Color4.h:233
Engine main class.
Definition: Engine.h:131
Texture entity.
Definition: Texture.h:24
GUI parser.
Definition: GUIParser.h:40
static const Properties * getEngineThemeProperties()
Definition: GUIParser.h:54
GUI module class.
Definition: GUI.h:64
static STATIC_DLL_IMPEXT GUIColor GUICOLOR_EFFECT_COLOR_ADD
Definition: GUIColor.h:36
static STATIC_DLL_IMPEXT GUIColor GUICOLOR_EFFECT_COLOR_MUL
Definition: GUIColor.h:35
GUI element node conditions.
static STATIC_DLL_IMPEXT GUINode_AlignmentHorizontal * RIGHT
static STATIC_DLL_IMPEXT GUINode_AlignmentHorizontal * CENTER
static STATIC_DLL_IMPEXT GUINode_AlignmentHorizontal * LEFT
static STATIC_DLL_IMPEXT GUINode_AlignmentVertical * BOTTOM
static STATIC_DLL_IMPEXT GUINode_AlignmentVertical * CENTER
static STATIC_DLL_IMPEXT GUINode_AlignmentVertical * TOP
static STATIC_DLL_IMPEXT GUINode_RequestedConstraints_RequestedConstraintsType * AUTO
GUI node base class.
Definition: GUINode.h:64
float computeParentChildrenRenderOffsetXTotal()
Definition: GUINode.cpp:952
GUINode_Border border
Definition: GUINode.h:160
virtual void render(GUIRenderer *guiRenderer)
Render.
Definition: GUINode.cpp:509
GUIParentNode * parentNode
Definition: GUINode.h:148
GUINode_ComputedConstraints computedConstraints
Definition: GUINode.h:152
GUINode_Padding padding
Definition: GUINode.h:159
GUINodeController * getController()
Definition: GUINode.h:661
GUIScreenNode * screenNode
Definition: GUINode.h:147
virtual int getAutoWidth()
Definition: GUINode.cpp:161
GUINode_RequestedConstraints requestedConstraints
Definition: GUINode.h:151
float computeParentChildrenRenderOffsetYTotal()
Definition: GUINode.cpp:962
bool shouldRender()
Returns if to render.
Definition: GUINode.h:302
virtual void dispose()
Dispose node.
Definition: GUINode.cpp:462
GUINode_Alignments alignments
Definition: GUINode.h:150
GUINode_ComputedConstraints & getComputedConstraints()
Definition: GUINode.h:412
GUI parent node base class thats supporting child nodes.
Definition: GUIParentNode.h:42
void setChildrenRenderOffsetY(float childrenRenderOffSetY)
Set children render offset y.
GUI screen node that represents a screen that can be rendered via GUI system.
Definition: GUIScreenNode.h:72
void invalidateLayout(GUINode *node)
Mark a node to be invalidated regarding layout.
GUIFont * getFont(const string &fileName, int size)
Get font.
Texture * getImage(const string &fileName)
Get image.
void setImage(int idx, const string &image, const string &url=string(), int width=-1, int height=-1, float horizontalScale=1.0f, float verticalScale=1.0f, const GUIColor &effectColorMul=GUIColor::GUICOLOR_EFFECT_COLOR_MUL, const GUIColor &effectColorAdd=GUIColor::GUICOLOR_EFFECT_COLOR_ADD)
Set image.
void dispose() override
Dispose node.
void insertText(int32_t idx, int c)
Insert character c at idx.
void unsetTextStyle(int startIdx, int endIdx)
Unset text style.
void setIndexMousePosition(int x, int y)
Set index mouse position.
void setSelectionIndexMousePosition(int x, int y)
Set selection index mouse position.
void setText(const MutableString &text)
Set text.
void unsetStyles()
Unset/dispose styles.
void setTextStyle(int startIdx, int endIdx, const GUIColor &color, const string &font=string(), int size=-1, const string &url=string())
Set text style.
void removeText(int32_t idx, int32_t count)
Remove characters at idx with given length.
void scrollToSelectionIndex()
Set scroll to selection index.
void computeContentAlignment() override
Do content alignment.
void unsetIndexMousePosition()
Unset index mouse position.
void render(GUIRenderer *guiRenderer) override
Render.
void unsetSelectionIndexMousePosition()
Unset selection index mouse position.
TextStyle * getTextStyle(const vector< int > &lineCharIdxs, int lineCharIdx, int &textStyleIdx)
Get text style for.
void scrollToIndex()
Set scroll to index.
void computeContentAlignmentInternal()
Compute content alignment internal.
void determineNextLineConstraints(UTF8CharacterIterator &u8It, int charEndIdx, int textStyleIdx)
Determine next line constraints.
GUI font class.
Definition: GUIFont.h:41
GUICharacter * getCharacter(uint32_t charId)
Get character defintion.
Definition: GUIFont.h:140
void drawCharacter(GUIRenderer *guiRenderer, GUICharacter *character, int x, int y, const GUIColor &color=GUIColor::GUICOLOR_WHITE)
Draw character.
Definition: GUIFont.cpp:275
void drawCharacterBackground(GUIRenderer *guiRenderer, GUICharacter *character, int x, int y, int lineHeight, const GUIColor &color)
Draw background.
Definition: GUIFont.cpp:314
const GUIColor & getGUIEffectColorAdd()
Definition: GUIRenderer.h:214
void setGUIEffectColorMul(const GUIColor &color)
Set GUI effect color mul.
Definition: GUIRenderer.h:207
void setGUIEffectColorAdd(const GUIColor &color)
Set GUI effect color add.
Definition: GUIRenderer.h:222
void bindTexture(int32_t textureId)
Bind texture.
bool isQuadVisible2(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)
Definition: GUIRenderer.h:443
void addQuad(float x1, float y1, float colorR1, float colorG1, float colorB1, float colorA1, float tu1, float tv1, float x2, float y2, float colorR2, float colorG2, float colorB2, float colorA2, float tu2, float tv2, float x3, float y3, float colorR3, float colorG3, float colorB3, float colorA3, float tu3, float tv3, float x4, float y4, float colorR4, float colorG4, float colorB4, float colorA4, float tu4, float tv4, bool solidColor=false, bool rotated=false)
Add quad Note: quad vertices order 1 2 +-—+ | | | | +-—+ 4 3.
Definition: GUIRenderer.h:507
const GUIColor & getGUIEffectColorMul()
Definition: GUIRenderer.h:199
Character class.
Definition: Character.h:17
Console class.
Definition: Console.h:29
Float class.
Definition: Float.h:27
Mutable utf8 aware string class.
Definition: MutableString.h:23
MutableString & append(char c)
Append character.
char getCharAt(int32_t idx) const
Get char at given binary index.
Definition: MutableString.h:78
const UTF8CharacterIterator getUTF8CharacterIterator() const
int getUtf8BinaryIndex(int idx) const
MutableString & remove(int32_t idx, int32_t count, int *binaryCount=nullptr)
Remove characters at idx with given length.
const string & getString() const
MutableString & reset()
Reset.
Definition: MutableString.h:95
MutableString & insert(int32_t idx, char c)
Insert character c at idx.
Properties class, which helps out with storeing or loading key value pairs from/to property files.
Definition: Properties.h:23
String tools class.
Definition: StringTools.h:22
UTF8 string character iterator.
void seekBinaryPosition(int position) const
Set underlying binary buffer position.
std::exception Exception
Exception base class.
Definition: Exception.h:18
GUINode_AlignmentVertical * vertical
GUINode_AlignmentHorizontal * horizontal
GUI node border entity.
GUI node padding entity.
GUINode_RequestedConstraints_RequestedConstraintsType * widthType
GUINode_RequestedConstraints_RequestedConstraintsType * heightType
GUI node scale 9 grid entity.