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