1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2011 by The Allacrost Project
3 //            Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 //                         All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See http://www.gnu.org/copyleft/gpl.html for details.
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 /** ****************************************************************************
12 *** \file    textbox.h
13 *** \author  Raj Sharma, roos@allacrost.org
14 *** \author  Yohann Ferreira, yohann ferreira orange fr
15 *** \brief   Header file for TextBox class
16 *** ***************************************************************************/
17 
18 #include "textbox.h"
19 
20 #include "common/gui/menu_window.h"
21 #include "engine/video/video.h"
22 
23 #include "utils/utils_common.h"
24 
25 #include <cassert>
26 
27 using namespace vt_utils;
28 using namespace vt_video;
29 using namespace vt_video::private_video;
30 using namespace vt_gui;
31 using namespace vt_gui::private_gui;
32 
33 namespace vt_gui
34 {
35 
36 //! \brief The unicode version of the newline character, used for string parsing
37 const uint16_t NEWLINE_CHARACTER = static_cast<uint16_t>('\n');
38 
TextBox()39 TextBox::TextBox() :
40     _display_speed(DEFAULT_MESSAGE_SPEED),
41     _text_xalign(VIDEO_X_LEFT),
42     _text_yalign(VIDEO_Y_BOTTOM),
43     _num_chars(0),
44     _finished(false),
45     _current_time(0),
46     _end_time(0),
47     _mode(VIDEO_TEXT_INSTANT),
48     _text_height(0.0f),
49     _text_pos(0.0f, 0.0f)
50 {
51     _width = 0.0f;
52     _height = 0.0f;
53     _text_style = TextManager->GetDefaultStyle();
54 }
55 
TextBox(float x,float y,float width,float height,const TEXT_DISPLAY_MODE & mode)56 TextBox::TextBox(float x, float y, float width, float height, const TEXT_DISPLAY_MODE &mode) :
57     _display_speed(DEFAULT_MESSAGE_SPEED),
58     _text_xalign(VIDEO_X_LEFT),
59     _text_yalign(VIDEO_Y_BOTTOM),
60     _num_chars(0),
61     _finished(false),
62     _current_time(0),
63     _end_time(0),
64     _mode(mode),
65     _text_height(0.0f),
66     _text_pos(0.0f, 0.0f)
67 {
68     _width = width;
69     _height = height;
70     _text_style = TextManager->GetDefaultStyle();
71     SetPosition(x, y);
72 }
73 
ClearText()74 void TextBox::ClearText()
75 {
76     _finished = true;
77     _text.clear();
78     _num_chars = 0;
79     _text_save.clear();
80     _text_image.Clear();
81 }
82 
Update(uint32_t time)83 void TextBox::Update(uint32_t time)
84 {
85     if (_finished)
86         return;
87 
88     _current_time += time;
89 
90     if(_text.empty() == false && _current_time > _end_time)
91         _finished = true;
92 }
93 
Draw()94 void TextBox::Draw()
95 {
96     if(_mode != VIDEO_TEXT_INSTANT && _text.empty())
97         return;
98 
99     // Don't draw text window if parent window is hidden
100     if(_owner && _owner->GetState() == VIDEO_MENU_STATE_HIDDEN)
101         return;
102 
103     VideoManager->PushState();
104 
105     VideoManager->SetDrawFlags(_xalign, _yalign, VIDEO_BLEND, 0);
106 
107     // Set the draw cursor, draw flags, and draw the text
108     if (_mode == VIDEO_TEXT_INSTANT) {
109         VideoManager->Move(_text_pos.x, _text_pos.y);
110         VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_TOP, VIDEO_BLEND, 0);
111         _text_image.Draw();
112     }
113     else {
114         VideoManager->Move(0.0f, _text_pos.y);
115         VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_TOP, VIDEO_BLEND, 0);
116         _DrawTextLines(_text_pos.x, _text_pos.y, _scissor_rect);
117     }
118 
119     if(GUIManager->DEBUG_DrawOutlines())
120         _DEBUG_DrawOutline();
121 
122     VideoManager->PopState();
123 }
124 
SetDimensions(float w,float h)125 void TextBox::SetDimensions(float w, float h)
126 {
127     if(w <= 0.0f || w > 1024.0f) {
128         IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid width argument: " << w << std::endl;
129         return;
130     }
131 
132     if(h <= 0.0f || h > 768.0f) {
133         IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid height argument: " << h << std::endl;
134         return;
135     }
136 
137     _width = w;
138     _height = h;
139     _ReformatText();
140 }
141 
SetTextAlignment(int32_t xalign,int32_t yalign)142 void TextBox::SetTextAlignment(int32_t xalign, int32_t yalign)
143 {
144     _text_xalign = xalign;
145     _text_yalign = yalign;
146     _ReformatText();
147 }
148 
SetTextStyle(const TextStyle & style)149 void TextBox::SetTextStyle(const TextStyle &style)
150 {
151     if(style.GetFontProperties() == nullptr) {
152         IF_PRINT_WARNING(VIDEO_DEBUG) << "Function failed because it was passed an invalid font name: " << style.GetFontName() << std::endl;
153         return;
154     }
155 
156     _text_style = style;
157     _text_image.SetStyle(style);
158 
159     _ReformatText();
160 }
161 
SetDisplayMode(const TEXT_DISPLAY_MODE & mode)162 void TextBox::SetDisplayMode(const TEXT_DISPLAY_MODE &mode)
163 {
164     if(mode < VIDEO_TEXT_INSTANT || mode >= VIDEO_TEXT_TOTAL) {
165         IF_PRINT_WARNING(VIDEO_DEBUG) << "function failed because of an invalid mode argument: " << mode << std::endl;
166         return;
167     }
168 
169     _mode = mode;
170 }
171 
SetDisplaySpeed(float display_speed)172 void TextBox::SetDisplaySpeed(float display_speed)
173 {
174     // Avoid strange behaviours by not permitting under 1.0f.
175     if(display_speed < 1.0f) {
176         PRINT_WARNING << "TextBox::SetDisplaySpeed() failed due to an invalid display speed: "
177                       << display_speed << std::endl;
178         return;
179     }
180 
181     _display_speed = display_speed;
182 }
183 
SetDisplayText(const std::string & text)184 void TextBox::SetDisplayText(const std::string &text)
185 {
186     SetDisplayText(MakeUnicodeString(text));
187 }
188 
SetDisplayText(const ustring & text)189 void TextBox::SetDisplayText(const ustring &text)
190 {
191     // If the text hasn't changed, don't recompute the textbox.
192     if (_text_save == text)
193         return;
194 
195     _text_save = text;
196     _ReformatText();
197 
198     // Reset the timer since new text has been set
199     _current_time = 0;
200 
201     // Determine how much time the text will take to display depending on the display mode, speed, and size of the text
202     _finished = false;
203     switch(_mode) {
204     default:
205         _mode = VIDEO_TEXT_INSTANT;
206         /* Falls through. */
207     case VIDEO_TEXT_INSTANT:
208         _end_time = 0;
209         // Set finished to true only if the display mode is VIDEO_TEXT_INSTANT
210         _finished = true;
211         break;
212 
213     case VIDEO_TEXT_CHAR:     // All three of these modes display one character at a time
214     case VIDEO_TEXT_FADECHAR:
215     case VIDEO_TEXT_REVEAL:
216         // We desire the total number of milliseconds to render the string.
217         // Display speed is in character per second, so cancel the character term and multiply by 1000 to get ms
218         _end_time = static_cast<int32_t>(1000.0f * _num_chars / _display_speed);
219         break;
220 
221     case VIDEO_TEXT_FADELINE:   // Displays one line at a time
222         // Instead of _num_chars in the other calculation, we use number of lines times CHARS_PER_LINE
223         _end_time = static_cast<int32_t>(1000.0f * (_text.size() * CHARS_PER_LINE) / _display_speed);
224         break;
225     };
226 
227 }
228 
_ReformatText()229 void TextBox::_ReformatText()
230 {
231     // Go through the text ustring and determine where the newline characters can be found,
232     // examining one line at a time and adding it to the _text vector.
233     _text.clear();
234     _num_chars = 0;
235 
236     FontProperties* fp = _text_style.GetFontProperties();
237 
238     // If font not set, return (leave _text vector empty)
239     if(fp == nullptr || fp->ttf_font == nullptr) {
240         IF_PRINT_WARNING(VIDEO_DEBUG) << "Textbox font properties are invalid" << std::endl;
241         return;
242     }
243 
244     if (_mode == VIDEO_TEXT_INSTANT) {
245         _text_image.SetWordWrapWidth(_width);
246         _text_image.SetText(_text_save);
247     }
248     else {
249         // Get the wrapped text lines
250         _text = TextManager->WrapText(_text_save, fp->ttf_font, _width);
251 
252         // Compute the number of chars
253         const size_t temp_length = _text_save.length();
254         size_t startline_pos = 0;
255         uint32_t new_lines = 0;
256         while(startline_pos < temp_length) {
257             size_t newline_pos = _text_save.find(NEWLINE_CHARACTER, startline_pos);
258             if(newline_pos == ustring::npos)
259                 break;
260             ++new_lines;
261             startline_pos = newline_pos + 1;
262         }
263         _num_chars = _text_save.length() - new_lines;
264     }
265 
266     // Update the scissor cache.
267     // Stores the positions of the four sides of the rectangle.
268     float left   = 0.0f;
269     float right  = _width;
270     float bottom = 0.0f;
271     float top    = _height;
272 
273     VideoManager->PushState();
274 
275     VideoManager->SetDrawFlags(_xalign, _yalign, VIDEO_BLEND, 0);
276     CalculateAlignedRect(left, right, bottom, top);
277 
278     VideoManager->PopState();
279 
280     // Create a screen rectangle for the position and apply any scissoring.
281     int32_t x = 0, y = 0, w = 0, h = 0;
282 
283     x = static_cast<int32_t>(left < right ? left : right);
284     y = static_cast<int32_t>(top < bottom ? top : bottom);
285     w = static_cast<int32_t>(right - left);
286     h = static_cast<int32_t>(top - bottom);
287 
288     if (w < 0)
289         w = -w;
290     if (h < 0)
291         h = -h;
292 
293     _scissor_rect.Set(x, y, w, h);
294 
295     // Update the text height.
296     _text_height = _CalculateTextHeight();
297 
298     // Calculate the height of the text and check it against the height of the textbox.
299     if (_text_height > _height) {
300         IF_PRINT_WARNING(VIDEO_DEBUG) << "tried to display text of height (" << _text_height
301                                       << ") in a window of lower height (" << _height << ")" << std::endl;
302     }
303 
304     // Determine the vertical position of the text based on the alignment.
305     if (_text_yalign == VIDEO_Y_TOP) {
306         _text_pos.y = top;
307     } else if (_text_yalign == VIDEO_Y_CENTER) {
308         _text_pos.y = top - (VideoManager->_current_context.coordinate_system.GetVerticalDirection()
309                       * (_height - _text_height) * 0.5f);
310     } else { // (_yalign == VIDEO_Y_BOTTOM)
311         _text_pos.y = top - (VideoManager->_current_context.coordinate_system.GetVerticalDirection()
312                       * (_height - _text_height));
313     }
314 
315     // Determine the horizontal position of the text based on the alignment.
316     if (_text_xalign == VIDEO_X_LEFT) {
317         _text_pos.x = left;
318     } else if (_text_xalign == VIDEO_X_CENTER) {
319         _text_pos.x = (left + right - _text_image.GetWidth()) * 0.5f;
320     } else { // (_text_xalign == VIDEO_X_RIGHT)
321         _text_pos.x = right;
322     }
323 }
324 
_CalculateTextHeight()325 float TextBox::_CalculateTextHeight()
326 {
327     if (_mode == VIDEO_TEXT_INSTANT)
328         return _text_image.GetHeight();
329 
330     if (_text.empty())
331         return 0;
332 
333     FontProperties* font_properties = _text_style.GetFontProperties();
334     assert(font_properties != nullptr);
335     return static_cast<float>(font_properties->height + font_properties->line_skip * (_text.size() - 1));
336 }
337 
_DrawTextLines(float text_x,float text_y,ScreenRect scissor_rect)338 void TextBox::_DrawTextLines(float text_x, float text_y, ScreenRect scissor_rect)
339 {
340     FontProperties* fp = _text_style.GetFontProperties();
341     TTF_Font* ttf_font = fp->ttf_font;
342     int32_t num_chars_drawn = 0;
343 
344     // Calculate the fraction of the text to display
345     float percent_complete;
346     if(_finished)
347         percent_complete = 1.0f;
348     else
349         percent_complete = static_cast<float>(_current_time) / static_cast<float>(_end_time);
350 
351     // Iterate through the loop for every line of text and draw it
352     for(int32_t line = 0; line < static_cast<int32_t>(_text.size()); ++line) {
353         // (1): Calculate the x draw offset for this line and move to that position
354         float line_width = static_cast<float>(TextManager->CalculateTextWidth(ttf_font, _text[line]));
355         int32_t x_align = VideoManager->_ConvertXAlign(_text_xalign);
356         float x_offset = text_x + ((x_align + 1) * line_width) * 0.5f * VideoManager->_current_context.coordinate_system.GetHorizontalDirection();
357 
358         VideoManager->MoveRelative(x_offset, 0.0f);
359 
360         int32_t line_size = static_cast<int32_t>(_text[line].size());
361 
362         // (2): Draw the text depending on the display mode and whether or not the gradual display is finished
363         if(_finished || _mode == VIDEO_TEXT_INSTANT) {
364             TextManager->Draw(_text[line], _text_style);
365         }
366         else if(_mode == VIDEO_TEXT_CHAR) {
367             // Determine which character is currently being rendered
368             int32_t cur_char = static_cast<int32_t>(percent_complete * _num_chars);
369 
370             // If the current character to draw is after this line, render the entire line
371             if(num_chars_drawn + line_size < cur_char) {
372                 TextManager->Draw(_text[line], _text_style);
373             }
374             // The current character to draw is on this line: figure out which characters on this line should be drawn
375             else {
376                 int32_t num_completed_chars = cur_char - num_chars_drawn;
377                 if(num_completed_chars > 0) {
378                     ustring substring = _text[line].substr(0, num_completed_chars);
379                     TextManager->Draw(substring);
380                 }
381             }
382         } // else if (_mode == VIDEO_TEXT_CHAR)
383 
384         else if(_mode == VIDEO_TEXT_FADECHAR) {
385             // Figure out which character is currently being rendered
386             float fade_cur_char = percent_complete * _num_chars;
387             int32_t cur_char = static_cast<int32_t>(fade_cur_char);
388             float cur_percent = fade_cur_char - cur_char;
389 
390             // If the current character to draw is after this line, draw the whole line
391             if(num_chars_drawn + line_size <= cur_char) {
392                 TextManager->Draw(_text[line], _text_style);
393             }
394             // The current character is on this line: draw any previous characters on this line as well as the current character
395             else {
396                 int32_t num_completed_chars = cur_char - num_chars_drawn;
397 
398                 // Continue only if this line has at least one character that should be drawn
399                 if(num_completed_chars >= 0) {
400                     ustring substring;
401 
402                     // Draw any fully completed characters at full opacity
403                     if(num_completed_chars > 0) {
404                         substring = _text[line].substr(0, num_completed_chars);
405                         TextManager->Draw(substring, _text_style);
406                     }
407 
408                     // Draw the current character that is being faded in at the appropriate alpha level
409                     Color saved_color = _text_style.GetColor();
410                     Color current_color = saved_color;
411                     current_color[3] *= cur_percent;
412                     _text_style.SetColor(current_color);
413 
414                     VideoManager->MoveRelative(static_cast<float>(TextManager->CalculateTextWidth(ttf_font, substring)), 0.0f);
415                     TextManager->Draw(_text[line].substr(num_completed_chars, 1), _text_style);
416                     _text_style.SetColor(saved_color);
417                 }
418             }
419         } // else if (_mode == VIDEO_TEXT_FADECHAR)
420 
421         else if(_mode == VIDEO_TEXT_FADELINE) {
422             // Deteremine which line is currently being rendered
423             float fade_lines = percent_complete * _text.size();
424             int32_t lines = static_cast<int32_t>(fade_lines);
425             float cur_percent = fade_lines - lines;
426 
427             // If this line comes before the line being rendered, simply draw the line and be done with it
428             if(line < lines) {
429                 TextManager->Draw(_text[line], _text_style);
430             }
431             // Otherwise if this is the line being rendered, determine the amount of alpha for the line being faded in and draw it
432             else if(line == lines) {
433                 Color saved_color = _text_style.GetColor();
434                 Color current_color = saved_color;
435                 current_color[3] *= cur_percent;
436                 _text_style.SetColor(current_color);
437 
438                 TextManager->Draw(_text[line], _text_style);
439                 _text_style.SetColor(saved_color);
440             }
441         } // else if (_mode == VIDEO_TEXT_FADELINE)
442 
443         else if(_mode == VIDEO_TEXT_REVEAL) {
444             // Determine which character is currently being rendered
445             float fade_cur_char = percent_complete * _num_chars;
446             int32_t cur_char = static_cast<int32_t>(fade_cur_char);
447             float cur_percent = fade_cur_char - cur_char;
448             int32_t num_completed_chars = cur_char - num_chars_drawn;
449 
450             // If the current character comes after this line, simply render the entire line
451             if(num_chars_drawn + line_size <= cur_char) {
452                 TextManager->Draw(_text[line], _text_style);
453             }
454             // If the line contains the current character, draw all previous characters as well as the current one
455             else if(num_completed_chars >= 0) {
456                 ustring substring;
457 
458                 // If there are already completed characters on this line, draw them in full
459                 if(num_completed_chars > 0) {
460                     substring = _text[line].substr(0, num_completed_chars);
461                     TextManager->Draw(substring, _text_style);
462                 }
463 
464                 // Now draw the current character from the line, partially scissored according to the amount that is complete
465                 ustring cur_char_string = _text[line].substr(num_completed_chars, 1);
466 
467                 // Create a rectangle for the current character, in window coordinates
468                 int32_t char_x, char_y, char_w, char_h;
469                 char_x = static_cast<int32_t>(x_offset + VideoManager->_current_context.coordinate_system.GetHorizontalDirection()
470                                             * TextManager->CalculateTextWidth(ttf_font, substring));
471                 char_y = static_cast<int32_t>(text_y - VideoManager->_current_context.coordinate_system.GetVerticalDirection()
472                                             * (fp->height + fp->descent));
473 
474                 if(VideoManager->_current_context.coordinate_system.GetHorizontalDirection() < 0.0f)
475                     char_y = static_cast<int32_t>(VideoManager->_current_context.coordinate_system.GetBottom()) - char_y;
476 
477                 if(VideoManager->_current_context.coordinate_system.GetVerticalDirection() < 0.0f)
478                     char_x = static_cast<int32_t>(VideoManager->_current_context.coordinate_system.GetLeft()) - char_x;
479 
480                 char_w = TextManager->CalculateTextWidth(ttf_font, cur_char_string);
481                 char_h = fp->height;
482 
483                 // Multiply the width by percentage done to determine the scissoring dimensions
484                 char_w = static_cast<int32_t>(cur_percent * char_w);
485                 VideoManager->MoveRelative(VideoManager->_current_context.coordinate_system.GetHorizontalDirection()
486                                            * TextManager->CalculateTextWidth(ttf_font, substring), 0.0f);
487 
488                 // Construct the scissor rectangle using the character dimensions and draw the revealing character.
489                 VideoManager->PushState();
490 
491                 ScreenRect char_scissor_rect(char_x, char_y, char_w, char_h);
492                 scissor_rect.Intersect(char_scissor_rect);
493 
494                 VideoManager->EnableScissoring();
495 
496                 // Convert to screen coordinates.
497                 scissor_rect.left = static_cast<int32_t>(scissor_rect.left / VIDEO_STANDARD_RES_WIDTH * VideoManager->_current_context.viewport.width);
498                 scissor_rect.top = static_cast<int32_t>(scissor_rect.top / VIDEO_STANDARD_RES_HEIGHT * VideoManager->_current_context.viewport.height);
499                 scissor_rect.width = static_cast<int32_t>(scissor_rect.width / VIDEO_STANDARD_RES_WIDTH * VideoManager->_current_context.viewport.width);
500                 scissor_rect.height = static_cast<int32_t>(scissor_rect.height / VIDEO_STANDARD_RES_HEIGHT * VideoManager->_current_context.viewport.height);
501                 VideoManager->SetScissorRect(scissor_rect);
502 
503                 TextManager->Draw(cur_char_string, _text_style);
504 
505                 VideoManager->PopState();
506             }
507             // In the else case, the current character is before the line, so we don't draw anything for this line at all
508         } // else if (_mode == VIDEO_TEXT_REVEAL)
509 
510         else {
511             // Invalid display mode: just render the text instantly
512             TextManager->Draw(_text[line]);
513             IF_PRINT_WARNING(VIDEO_DEBUG) << "an unknown/unsupported text display mode was active: " << _mode << std::endl;
514         }
515 
516         // (3): Prepare to draw the next line and move the draw cursor appropriately
517         num_chars_drawn += line_size;
518         // VideoManager->MoveRelative(-xOffset, fp.line_skip * -cs._vertical_direction);
519         text_y += fp->line_skip * -VideoManager->_current_context.coordinate_system.GetVerticalDirection();
520         VideoManager->Move(0.0f, text_y);
521     }
522 }
523 
_DEBUG_DrawOutline()524 void TextBox::_DEBUG_DrawOutline()
525 {
526     // Stores the positions of the four sides of the rectangle
527     float left   = 0.0f;
528     float right  = _width;
529     float bottom = 0.0f;
530     float top    = _height;
531 
532     // Draw the outline of the textbox.
533     VideoManager->Move(0.0f, 0.0f);
534     CalculateAlignedRect(left, right, bottom, top);
535     VideoManager->DrawRectangleOutline(left, right, bottom, top, 3, alpha_black);
536     VideoManager->DrawRectangleOutline(left, right, bottom, top, 1, alpha_white);
537 
538     // Draw the inner boundaries for each line of text.
539     FontProperties* fp = _text_style.GetFontProperties();
540     uint32_t possible_lines = _height / fp->line_skip;
541     float line_height = fp->line_skip * -VideoManager->_current_context.coordinate_system.GetVerticalDirection();
542     float line_offset = top;
543 
544     for (uint32_t i = 1; i <= possible_lines; ++i) {
545         line_offset += line_height;
546         VideoManager->DrawLine(left, line_offset, 3, right, line_offset, 3, alpha_black);
547         VideoManager->DrawLine(left, line_offset, 1, right, line_offset, 1, alpha_white);
548     }
549 }
550 
551 }  // namespace vt_gui
552