Refactor to centralize GUIButton styling/rendering code (#9090)
[oweals/minetest.git] / src / irrlicht_changes / static_text.cpp
1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
2 // Copyright (C) 2016 NathanaĆ«l Courant:
3 //   Modified the functions to use EnrichedText instead of string.
4 // This file is part of the "Irrlicht Engine".
5 // For conditions of distribution and use, see copyright notice in irrlicht.h
6
7 #include "static_text.h"
8 #ifdef _IRR_COMPILE_WITH_GUI_
9
10 #include <IGUIFont.h>
11 #include <IVideoDriver.h>
12 #include <rect.h>
13 #include <SColor.h>
14
15 #if USE_FREETYPE
16         #include "CGUITTFont.h"
17 #endif
18
19 #include "util/string.h"
20
21 namespace irr
22 {
23
24 #if USE_FREETYPE
25
26 namespace gui
27 {
28 //! constructor
29 StaticText::StaticText(const EnrichedString &text, bool border,
30                         IGUIEnvironment* environment, IGUIElement* parent,
31                         s32 id, const core::rect<s32>& rectangle,
32                         bool background)
33 : IGUIStaticText(environment, parent, id, rectangle),
34         HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT),
35         Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background),
36         RestrainTextInside(true), RightToLeft(false),
37         OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)),
38         OverrideFont(0), LastBreakFont(0)
39 {
40         #ifdef _DEBUG
41         setDebugName("StaticText");
42         #endif
43
44         Text = text.c_str();
45         cText = text;
46         if (environment && environment->getSkin())
47         {
48                 BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE);
49         }
50 }
51
52
53 //! destructor
54 StaticText::~StaticText()
55 {
56         if (OverrideFont)
57                 OverrideFont->drop();
58 }
59
60 //! draws the element and its children
61 void StaticText::draw()
62 {
63         if (!IsVisible)
64                 return;
65
66         IGUISkin* skin = Environment->getSkin();
67         if (!skin)
68                 return;
69         video::IVideoDriver* driver = Environment->getVideoDriver();
70
71         core::rect<s32> frameRect(AbsoluteRect);
72
73         // draw background
74
75         if (Background)
76         {
77                 if ( !OverrideBGColorEnabled )  // skin-colors can change
78                         BGColor = skin->getColor(gui::EGDC_3D_FACE);
79
80                 driver->draw2DRectangle(BGColor, frameRect, &AbsoluteClippingRect);
81         }
82
83         // draw the border
84
85         if (Border)
86         {
87                 skin->draw3DSunkenPane(this, 0, true, false, frameRect, &AbsoluteClippingRect);
88                 frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X);
89         }
90
91         // draw the text
92         if (cText.size())
93         {
94                 IGUIFont* font = getActiveFont();
95
96                 if (font)
97                 {
98                         if (!WordWrap)
99                         {
100                                 // TODO: add colors here
101                                 if (VAlign == EGUIA_LOWERRIGHT)
102                                 {
103                                         frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y -
104                                                 font->getDimension(L"A").Height - font->getKerningHeight();
105                                 }
106                                 if (HAlign == EGUIA_LOWERRIGHT)
107                                 {
108                                         frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
109                                                 font->getDimension(cText.c_str()).Width;
110                                 }
111
112 #if USE_FREETYPE
113                                 if (font->getType() == irr::gui::EGFT_CUSTOM) {
114                                         irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
115                                         tmp->draw(Text, frameRect,
116                                                 OverrideColorEnabled ? OverrideColor :
117                                                         skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
118                                                 HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER,
119                                                 (RestrainTextInside ? &AbsoluteClippingRect : NULL));
120                                 } else
121 #endif
122                                 {
123                                         font->draw(Text.c_str(), frameRect,
124                                                 skin->getColor(EGDC_BUTTON_TEXT),
125                                                 HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER,
126                                                 (RestrainTextInside ? &AbsoluteClippingRect : NULL));
127                                 }
128                         }
129                         else
130                         {
131                                 if (font != LastBreakFont)
132                                         breakText();
133
134                                 core::rect<s32> r = frameRect;
135                                 s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
136                                 s32 totalHeight = height * BrokenText.size();
137                                 if (VAlign == EGUIA_CENTER)
138                                 {
139                                         r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2);
140                                 }
141                                 else if (VAlign == EGUIA_LOWERRIGHT)
142                                 {
143                                         r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight;
144                                 }
145
146                                 irr::video::SColor previous_color(255, 255, 255, 255);
147                                 for (u32 i=0; i<BrokenText.size(); ++i)
148                                 {
149                                         if (HAlign == EGUIA_LOWERRIGHT)
150                                         {
151                                                 r.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
152                                                         font->getDimension(BrokenText[i].c_str()).Width;
153                                         }
154
155                                         EnrichedString str = BrokenText[i];
156
157                                         //str = colorizeText(BrokenText[i].c_str(), colors, previous_color);
158                                         //if (!colors.empty())
159                                         //      previous_color = colors[colors.size() - 1];
160
161 #if USE_FREETYPE
162                                         if (font->getType() == irr::gui::EGFT_CUSTOM) {
163                                                 irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
164                                                 tmp->draw(str,
165                                                         r, previous_color, // FIXME
166                                                         HAlign == EGUIA_CENTER, false,
167                                                         (RestrainTextInside ? &AbsoluteClippingRect : NULL));
168                                         } else
169 #endif
170                                         {
171                                                 // Draw non-colored text
172                                                 font->draw(str.c_str(),
173                                                         r, skin->getColor(EGDC_BUTTON_TEXT),
174                                                         HAlign == EGUIA_CENTER, false,
175                                                         (RestrainTextInside ? &AbsoluteClippingRect : NULL));
176                                         }
177
178
179                                         r.LowerRightCorner.Y += height;
180                                         r.UpperLeftCorner.Y += height;
181                                 }
182                         }
183                 }
184         }
185
186         IGUIElement::draw();
187 }
188
189
190 //! Sets another skin independent font.
191 void StaticText::setOverrideFont(IGUIFont* font)
192 {
193         if (OverrideFont == font)
194                 return;
195
196         if (OverrideFont)
197                 OverrideFont->drop();
198
199         OverrideFont = font;
200
201         if (OverrideFont)
202                 OverrideFont->grab();
203
204         breakText();
205 }
206
207 //! Gets the override font (if any)
208 IGUIFont * StaticText::getOverrideFont() const
209 {
210         return OverrideFont;
211 }
212
213 //! Get the font which is used right now for drawing
214 IGUIFont* StaticText::getActiveFont() const
215 {
216         if ( OverrideFont )
217                 return OverrideFont;
218         IGUISkin* skin = Environment->getSkin();
219         if (skin)
220                 return skin->getFont();
221         return 0;
222 }
223
224 //! Sets another color for the text.
225 void StaticText::setOverrideColor(video::SColor color)
226 {
227         OverrideColor = color;
228         OverrideColorEnabled = true;
229 }
230
231
232 //! Sets another color for the text.
233 void StaticText::setBackgroundColor(video::SColor color)
234 {
235         BGColor = color;
236         OverrideBGColorEnabled = true;
237         Background = true;
238 }
239
240
241 //! Sets whether to draw the background
242 void StaticText::setDrawBackground(bool draw)
243 {
244         Background = draw;
245 }
246
247
248 //! Gets the background color
249 video::SColor StaticText::getBackgroundColor() const
250 {
251         return BGColor;
252 }
253
254
255 //! Checks if background drawing is enabled
256 bool StaticText::isDrawBackgroundEnabled() const
257 {
258         return Background;
259 }
260
261
262 //! Sets whether to draw the border
263 void StaticText::setDrawBorder(bool draw)
264 {
265         Border = draw;
266 }
267
268
269 //! Checks if border drawing is enabled
270 bool StaticText::isDrawBorderEnabled() const
271 {
272         return Border;
273 }
274
275
276 void StaticText::setTextRestrainedInside(bool restrainTextInside)
277 {
278         RestrainTextInside = restrainTextInside;
279 }
280
281
282 bool StaticText::isTextRestrainedInside() const
283 {
284         return RestrainTextInside;
285 }
286
287
288 void StaticText::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
289 {
290         HAlign = horizontal;
291         VAlign = vertical;
292 }
293
294
295 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
296 const video::SColor& StaticText::getOverrideColor() const
297 #else
298 video::SColor StaticText::getOverrideColor() const
299 #endif
300 {
301         return OverrideColor;
302 }
303
304
305 //! Sets if the static text should use the overide color or the
306 //! color in the gui skin.
307 void StaticText::enableOverrideColor(bool enable)
308 {
309         OverrideColorEnabled = enable;
310 }
311
312
313 bool StaticText::isOverrideColorEnabled() const
314 {
315         return OverrideColorEnabled;
316 }
317
318
319 //! Enables or disables word wrap for using the static text as
320 //! multiline text control.
321 void StaticText::setWordWrap(bool enable)
322 {
323         WordWrap = enable;
324         breakText();
325 }
326
327
328 bool StaticText::isWordWrapEnabled() const
329 {
330         return WordWrap;
331 }
332
333
334 void StaticText::setRightToLeft(bool rtl)
335 {
336         if (RightToLeft != rtl)
337         {
338                 RightToLeft = rtl;
339                 breakText();
340         }
341 }
342
343
344 bool StaticText::isRightToLeft() const
345 {
346         return RightToLeft;
347 }
348
349
350 //! Breaks the single text line.
351 void StaticText::breakText()
352 {
353         if (!WordWrap)
354                 return;
355
356         BrokenText.clear();
357
358         IGUISkin* skin = Environment->getSkin();
359         IGUIFont* font = getActiveFont();
360         if (!font)
361                 return;
362
363         LastBreakFont = font;
364
365         EnrichedString line;
366         EnrichedString word;
367         EnrichedString whitespace;
368         s32 size = cText.size();
369         s32 length = 0;
370         s32 elWidth = RelativeRect.getWidth();
371         if (Border)
372                 elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X);
373         wchar_t c;
374
375         //std::vector<irr::video::SColor> colors;
376
377         // We have to deal with right-to-left and left-to-right differently
378         // However, most parts of the following code is the same, it's just
379         // some order and boundaries which change.
380         if (!RightToLeft)
381         {
382                 // regular (left-to-right)
383                 for (s32 i=0; i<size; ++i)
384                 {
385                         c = cText.getString()[i];
386                         bool lineBreak = false;
387
388                         if (c == L'\r') // Mac or Windows breaks
389                         {
390                                 lineBreak = true;
391                                 //if (Text[i+1] == L'\n') // Windows breaks
392                                 //{
393                                 //      Text.erase(i+1);
394                                 //      --size;
395                                 //}
396                                 c = '\0';
397                         }
398                         else if (c == L'\n') // Unix breaks
399                         {
400                                 lineBreak = true;
401                                 c = '\0';
402                         }
403
404                         bool isWhitespace = (c == L' ' || c == 0);
405                         if ( !isWhitespace )
406                         {
407                                 // part of a word
408                                 //word += c;
409                                 word.addChar(cText, i);
410                         }
411
412                         if ( isWhitespace || i == (size-1))
413                         {
414                                 if (word.size())
415                                 {
416                                         // here comes the next whitespace, look if
417                                         // we must break the last word to the next line.
418                                         const s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
419                                         //const std::wstring sanitized = removeEscapes(word.c_str());
420                                         const s32 wordlgth = font->getDimension(word.c_str()).Width;
421
422                                         if (wordlgth > elWidth)
423                                         {
424                                                 // This word is too long to fit in the available space, look for
425                                                 // the Unicode Soft HYphen (SHY / 00AD) character for a place to
426                                                 // break the word at
427                                                 int where = core::stringw(word.c_str()).findFirst( wchar_t(0x00AD) );
428                                                 if (where != -1)
429                                                 {
430                                                         EnrichedString first = word.substr(0, where);
431                                                         EnrichedString second = word.substr(where, word.size() - where);
432                                                         first.addCharNoColor(L'-');
433                                                         BrokenText.push_back(line + first);
434                                                         const s32 secondLength = font->getDimension(second.c_str()).Width;
435
436                                                         length = secondLength;
437                                                         line = second;
438                                                 }
439                                                 else
440                                                 {
441                                                         // No soft hyphen found, so there's nothing more we can do
442                                                         // break to next line
443                                                         if (length)
444                                                                 BrokenText.push_back(line);
445                                                         length = wordlgth;
446                                                         line = word;
447                                                 }
448                                         }
449                                         else if (length && (length + wordlgth + whitelgth > elWidth))
450                                         {
451                                                 // break to next line
452                                                 BrokenText.push_back(line);
453                                                 length = wordlgth;
454                                                 line = word;
455                                         }
456                                         else
457                                         {
458                                                 // add word to line
459                                                 line += whitespace;
460                                                 line += word;
461                                                 length += whitelgth + wordlgth;
462                                         }
463
464                                         word.clear();
465                                         whitespace.clear();
466                                 }
467
468                                 if ( isWhitespace && c != 0)
469                                 {
470                                         whitespace.addChar(cText, i);
471                                 }
472
473                                 // compute line break
474                                 if (lineBreak)
475                                 {
476                                         line += whitespace;
477                                         line += word;
478                                         BrokenText.push_back(line);
479                                         line.clear();
480                                         word.clear();
481                                         whitespace.clear();
482                                         length = 0;
483                                 }
484                         }
485                 }
486
487                 line += whitespace;
488                 line += word;
489                 BrokenText.push_back(line);
490         }
491         else
492         {
493                 // right-to-left
494                 for (s32 i=size; i>=0; --i)
495                 {
496                         c = cText.getString()[i];
497                         bool lineBreak = false;
498
499                         if (c == L'\r') // Mac or Windows breaks
500                         {
501                                 lineBreak = true;
502                                 //if ((i>0) && Text[i-1] == L'\n') // Windows breaks
503                                 //{
504                                 //      Text.erase(i-1);
505                                 //      --size;
506                                 //}
507                                 c = '\0';
508                         }
509                         else if (c == L'\n') // Unix breaks
510                         {
511                                 lineBreak = true;
512                                 c = '\0';
513                         }
514
515                         if (c==L' ' || c==0 || i==0)
516                         {
517                                 if (word.size())
518                                 {
519                                         // here comes the next whitespace, look if
520                                         // we must break the last word to the next line.
521                                         const s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
522                                         const s32 wordlgth = font->getDimension(word.c_str()).Width;
523
524                                         if (length && (length + wordlgth + whitelgth > elWidth))
525                                         {
526                                                 // break to next line
527                                                 BrokenText.push_back(line);
528                                                 length = wordlgth;
529                                                 line = word;
530                                         }
531                                         else
532                                         {
533                                                 // add word to line
534                                                 line = whitespace + line;
535                                                 line = word + line;
536                                                 length += whitelgth + wordlgth;
537                                         }
538
539                                         word.clear();
540                                         whitespace.clear();
541                                 }
542
543                                 if (c != 0)
544                                 //      whitespace = core::stringw(&c, 1) + whitespace;
545                                 whitespace = cText.substr(i, 1) + whitespace;
546
547                                 // compute line break
548                                 if (lineBreak)
549                                 {
550                                         line = whitespace + line;
551                                         line = word + line;
552                                         BrokenText.push_back(line);
553                                         line.clear();
554                                         word.clear();
555                                         whitespace.clear();
556                                         length = 0;
557                                 }
558                         }
559                         else
560                         {
561                                 // yippee this is a word..
562                                 //word = core::stringw(&c, 1) + word;
563                                 word = cText.substr(i, 1) + word;
564                         }
565                 }
566
567                 line = whitespace + line;
568                 line = word + line;
569                 BrokenText.push_back(line);
570         }
571 }
572
573
574 //! Sets the new caption of this element.
575 void StaticText::setText(const wchar_t* text)
576 {
577         setText(EnrichedString(text));
578 }
579
580 //! Sets the new caption of this element.
581 void StaticText::setText(const EnrichedString &text)
582 {
583         IGUIElement::setText(text.c_str());
584         cText = text;
585         if (text.hasBackground()) {
586                 setBackgroundColor(text.getBackground());
587         }
588         breakText();
589 }
590
591
592 void StaticText::updateAbsolutePosition()
593 {
594         IGUIElement::updateAbsolutePosition();
595         breakText();
596 }
597
598
599 //! Returns the height of the text in pixels when it is drawn.
600 s32 StaticText::getTextHeight() const
601 {
602         IGUIFont* font = getActiveFont();
603         if (!font)
604                 return 0;
605
606         s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
607
608         if (WordWrap)
609                 height *= BrokenText.size();
610
611         return height;
612 }
613
614
615 s32 StaticText::getTextWidth() const
616 {
617         IGUIFont * font = getActiveFont();
618         if(!font)
619                 return 0;
620
621         if(WordWrap)
622         {
623                 s32 widest = 0;
624
625                 for(u32 line = 0; line < BrokenText.size(); ++line)
626                 {
627                         s32 width = font->getDimension(BrokenText[line].c_str()).Width;
628
629                         if(width > widest)
630                                 widest = width;
631                 }
632
633                 return widest;
634         }
635         else
636         {
637                 return font->getDimension(cText.c_str()).Width;
638         }
639 }
640
641
642 //! Writes attributes of the element.
643 //! Implement this to expose the attributes of your element for
644 //! scripting languages, editors, debuggers or xml serialization purposes.
645 void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
646 {
647         IGUIStaticText::serializeAttributes(out,options);
648
649         out->addBool    ("Border",              Border);
650         out->addBool    ("OverrideColorEnabled",OverrideColorEnabled);
651         out->addBool    ("OverrideBGColorEnabled",OverrideBGColorEnabled);
652         out->addBool    ("WordWrap",            WordWrap);
653         out->addBool    ("Background",          Background);
654         out->addBool    ("RightToLeft",         RightToLeft);
655         out->addBool    ("RestrainTextInside",  RestrainTextInside);
656         out->addColor   ("OverrideColor",       OverrideColor);
657         out->addColor   ("BGColor",             BGColor);
658         out->addEnum    ("HTextAlign",          HAlign, GUIAlignmentNames);
659         out->addEnum    ("VTextAlign",          VAlign, GUIAlignmentNames);
660
661         // out->addFont ("OverrideFont",        OverrideFont);
662 }
663
664
665 //! Reads attributes of the element
666 void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
667 {
668         IGUIStaticText::deserializeAttributes(in,options);
669
670         Border = in->getAttributeAsBool("Border");
671         enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
672         OverrideBGColorEnabled = in->getAttributeAsBool("OverrideBGColorEnabled");
673         setWordWrap(in->getAttributeAsBool("WordWrap"));
674         Background = in->getAttributeAsBool("Background");
675         RightToLeft = in->getAttributeAsBool("RightToLeft");
676         RestrainTextInside = in->getAttributeAsBool("RestrainTextInside");
677         OverrideColor = in->getAttributeAsColor("OverrideColor");
678         BGColor = in->getAttributeAsColor("BGColor");
679
680         setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
681                       (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
682
683         // OverrideFont = in->getAttributeAsFont("OverrideFont");
684 }
685
686 } // end namespace gui
687
688 #endif // USE_FREETYPE
689
690 } // end namespace irr
691
692
693 #endif // _IRR_COMPILE_WITH_GUI_