1 /** @file finaletextwidget.cpp  InFine animation system, FinaleTextWidget.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2014 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "de_base.h"
22 #include "ui/infine/finaletextwidget.h"
23 
24 #include <cstring> // memcpy, memmove
25 #include <de/concurrency.h>
26 #include <de/memoryzone.h>
27 #include <de/timer.h>
28 #include "api_fontrender.h"
29 #include "ui/infine/finalepagewidget.h"
30 
31 #ifdef __CLIENT__
32 #  include "gl/gl_main.h"
33 #  include <de/GLInfo>
34 #endif
35 
36 using namespace de;
37 
DENG2_PIMPL_NOREF(FinaleTextWidget)38 DENG2_PIMPL_NOREF(FinaleTextWidget)
39 {
40     animatorvector4_t color;
41     uint pageColor    = 0;                ///< 1-based page color index. @c 0= Use our own color.
42     uint pageFont     = 0;                ///< 1-based page font index. @c 0= Use our own font.
43     int alignFlags    = ALIGN_TOPLEFT;    ///< @ref alignmentFlags
44     short textFlags   = DTF_ONLY_SHADOW;  ///< @ref drawTextFlags
45     int scrollWait    = 0;
46     int scrollTimer   = 0;                ///< Automatic scrolling upwards.
47     int cursorPos     = 0;
48     int wait          = 3;
49     int timer         = 0;
50     float lineHeight  = 11.f / 7 - 1;
51     fontid_t fontNum  = 0;
52     char *text        = nullptr;
53     bool animComplete = true;
54 
55     Impl()  { AnimatorVector4_Init(color, 1, 1, 1, 1); }
56     ~Impl() { Z_Free(text); }
57 
58 #ifdef __CLIENT__
59     static int textLineWidth(char const *text)
60     {
61         int width = 0;
62 
63         for (; *text; text++)
64         {
65             if (*text == '\n') break;
66             if (*text == '\\')
67             {
68                 if (!*++text)     break;
69                 if (*text == 'n') break;
70 
71                 if (*text >= '0' && *text <= '9')
72                     continue;
73                 if (*text == 'w' || *text == 'W' || *text == 'p' || *text == 'P')
74                     continue;
75             }
76             width += FR_CharWidth(*text);
77         }
78 
79         return width;
80     }
81 #endif
82 };
83 
FinaleTextWidget(String const & name)84 FinaleTextWidget::FinaleTextWidget(String const &name)
85     : FinaleWidget(name)
86     , d(new Impl)
87 {}
88 
~FinaleTextWidget()89 FinaleTextWidget::~FinaleTextWidget()
90 {}
91 
accelerate()92 void FinaleTextWidget::accelerate()
93 {
94     // Fill in the rest very quickly.
95     d->wait = -10;
96 }
97 
setCursorPos(int newPos)98 FinaleTextWidget &FinaleTextWidget::setCursorPos(int newPos)
99 {
100     d->cursorPos = de::max(0, newPos);
101     return *this;
102 }
103 
runTicks()104 void FinaleTextWidget::runTicks(/*timespan_t timeDelta*/)
105 {
106     FinaleWidget::runTicks(/*timeDelta*/);
107 
108     AnimatorVector4_Think(d->color);
109 
110     if (d->wait)
111     {
112         if (--d->timer <= 0)
113         {
114             if (d->wait > 0)
115             {
116                 // Positive wait: move cursor one position, wait again.
117                 d->cursorPos++;
118                 d->timer = d->wait;
119             }
120             else
121             {
122                 // Negative wait: move cursor several positions, don't wait.
123                 d->cursorPos += ABS(d->wait);
124                 d->timer = 1;
125             }
126         }
127     }
128 
129     if (d->scrollWait)
130     {
131         if (--d->scrollTimer <= 0)
132         {
133             d->scrollTimer = d->scrollWait;
134             setOriginY(origin()[1].target - 1, d->scrollWait);
135             //pos[1].target -= 1;
136             //pos[1].steps = d->scrollWait;
137         }
138     }
139 
140     // Is the text object fully visible?
141     d->animComplete = (!d->wait || d->cursorPos >= visLength());
142 }
143 
144 #ifdef __CLIENT__
draw(Vector3f const & offset)145 void FinaleTextWidget::draw(Vector3f const &offset)
146 {
147     if (!d->text) return;
148 
149     DENG_ASSERT_IN_MAIN_THREAD();
150     DENG_ASSERT_GL_CONTEXT_ACTIVE();
151 
152     DGL_MatrixMode(DGL_MODELVIEW);
153     DGL_PushMatrix();
154     //glScalef(.1f/SCREENWIDTH, .1f/SCREENWIDTH, 1);
155     DGL_Translatef(origin()[0].value + offset.x, origin()[1].value + offset.y, origin()[2].value + offset.z);
156 
157     if (angle().value != 0)
158     {
159         // Counter the VGA aspect ratio.
160         DGL_Scalef(1, 200.0f / 240.0f, 1);
161         DGL_Rotatef(angle().value, 0, 0, 1);
162         DGL_Scalef(1, 240.0f / 200.0f, 1);
163     }
164 
165     DGL_Scalef(scale()[0].value, scale()[1].value, scale()[2].value);
166     DGL_Enable(DGL_TEXTURE_2D);
167 
168     FR_SetFont(d->pageFont? page()->predefinedFont(d->pageFont - 1) : d->fontNum);
169 
170     // Set the normal color.
171     animatorvector3_t const *color;
172     if (d->pageColor == 0)
173         color = (animatorvector3_t const *)&d->color;
174     else
175         color = page()->predefinedColor(d->pageColor - 1);
176     FR_SetColor((*color)[CR].value, (*color)[CG].value, (*color)[CB].value);
177     FR_SetAlpha(d->color[CA].value);
178 
179     int x = 0, y = 0, ch, linew = -1;
180     char *ptr = d->text;
181     for (int cnt = 0; *ptr && (!d->wait || cnt < d->cursorPos); ptr++)
182     {
183         if (linew < 0)
184             linew = d->textLineWidth(ptr);
185 
186         ch = *ptr;
187         if (*ptr == '\n') // Newline? (see below for unescaped \n)
188         {
189             x = 0;
190             y += FR_CharHeight('A') * (1 + d->lineHeight);
191             linew = -1;
192             cnt++; // Include newlines in the wait count.
193             continue;
194         }
195 
196         if (*ptr == '\\') // Escape?
197         {
198             if (!*++ptr)
199                 break;
200 
201             // Change of color?
202             if (*ptr >= '0' && *ptr <= '9')
203             {
204                 uint colorIdx = *ptr - '0';
205                 if (colorIdx == 0)
206                     color = (animatorvector3_t const *)&d->color;
207                 else
208                     color = page()->predefinedColor(colorIdx - 1);
209                 FR_SetColor((*color)[CR].value, (*color)[CG].value, (*color)[CB].value);
210                 FR_SetAlpha(d->color[CA].value);
211                 continue;
212             }
213 
214             // 'w' = half a second wait, 'W' = second'f wait
215             if (*ptr == 'w' || *ptr == 'W') // Wait?
216             {
217                 if (d->wait)
218                     cnt += int(float(TICRATE) / d->wait / (*ptr == 'w' ? 2 : 1));
219                 continue;
220             }
221 
222             // 'p' = 5 second wait, 'P' = 10 second wait
223             if (*ptr == 'p' || *ptr == 'P') // Longer pause?
224             {
225                 if (d->wait)
226                     cnt += int(float(TICRATE) / d->wait * (*ptr == 'p' ? 5 : 10));
227                 continue;
228             }
229 
230             if (*ptr == 'n' || *ptr == 'N') // Newline?
231             {
232                 x = 0;
233                 y += FR_CharHeight('A') * (1 + d->lineHeight);
234                 linew = -1;
235                 cnt++; // Include newlines in the wait count.
236                 continue;
237             }
238 
239             if (*ptr == '_')
240                 ch = ' ';
241         }
242 
243         // Let'f do Y-clipping (in case of tall text blocks).
244         if (scale()[1].value * y + origin()[1].value >= -scale()[1].value * d->lineHeight &&
245            scale()[1].value * y + origin()[1].value < SCREENHEIGHT)
246         {
247             FR_DrawCharXY(ch, (d->alignFlags & ALIGN_LEFT) ? x : x - linew / 2, y);
248             x += FR_CharWidth(ch);
249         }
250 
251         ++cnt;
252     }
253 
254     DGL_Disable(DGL_TEXTURE_2D);
255 
256     DGL_MatrixMode(DGL_MODELVIEW);
257     DGL_PopMatrix();
258 }
259 #endif
260 
animationComplete() const261 bool FinaleTextWidget::animationComplete() const
262 {
263     return d->animComplete;
264 }
265 
266 /// @todo optimize: Cache this result.
visLength()267 int FinaleTextWidget::visLength()
268 {
269     int count = 0;
270     if (d->text)
271     {
272         float const secondLen = (d->wait? TICRATE / d->wait : 0);
273 
274         for (char const *ptr = d->text; *ptr; ptr++)
275         {
276             if (*ptr == '\\') // Escape?
277             {
278                 if (!*++ptr) break;
279 
280                 switch (*ptr)
281                 {
282                 case 'w':   count += secondLen / 2;   break;
283                 case 'W':   count += secondLen;       break;
284                 case 'p':   count += 5 * secondLen;   break;
285                 case 'P':   count += 10 * secondLen;  break;
286 
287                 default:
288                     if ((*ptr >= '0' && *ptr <= '9') || *ptr == 'n' || *ptr == 'N')
289                         continue;
290                 }
291             }
292             count++;
293         }
294     }
295     return count;
296 }
297 
text() const298 char const *FinaleTextWidget::text() const
299 {
300     return d->text;
301 }
302 
setText(char const * newText)303 FinaleTextWidget &FinaleTextWidget::setText(char const *newText)
304 {
305     Z_Free(d->text); d->text = nullptr;
306     if (newText && newText[0])
307     {
308         int len = (int)qstrlen(newText) + 1;
309         d->text = (char *) Z_Malloc(len, PU_APPSTATIC, 0);
310         std::memcpy(d->text, newText, len);
311     }
312     int const visLen = visLength();
313     if (d->cursorPos > visLen)
314     {
315         d->cursorPos = visLen;
316     }
317     return *this;
318 }
319 
font() const320 fontid_t FinaleTextWidget::font() const
321 {
322     return d->fontNum;
323 }
324 
setFont(fontid_t newFont)325 FinaleTextWidget &FinaleTextWidget::setFont(fontid_t newFont)
326 {
327     d->fontNum  = newFont;
328     d->pageFont = 0;
329     return *this;
330 }
331 
alignment() const332 int FinaleTextWidget::alignment() const
333 {
334     return d->alignFlags;
335 }
336 
setAlignment(int newAlignment)337 FinaleTextWidget &FinaleTextWidget::setAlignment(int newAlignment)
338 {
339     d->alignFlags = newAlignment;
340     return *this;
341 }
342 
lineHeight() const343 float FinaleTextWidget::lineHeight() const
344 {
345     return d->lineHeight;
346 }
347 
setLineHeight(float newLineHeight)348 FinaleTextWidget &FinaleTextWidget::setLineHeight(float newLineHeight)
349 {
350     d->lineHeight = newLineHeight;
351     return *this;
352 }
353 
scrollRate() const354 int FinaleTextWidget::scrollRate() const
355 {
356     return d->scrollWait;
357 }
358 
setScrollRate(int newRateInTics)359 FinaleTextWidget &FinaleTextWidget::setScrollRate(int newRateInTics)
360 {
361     d->scrollWait  = newRateInTics;
362     d->scrollTimer = 0;
363     return *this;
364 }
365 
typeInRate() const366 int FinaleTextWidget::typeInRate() const
367 {
368     return d->wait;
369 }
370 
setTypeInRate(int newRateInTics)371 FinaleTextWidget &FinaleTextWidget::setTypeInRate(int newRateInTics)
372 {
373     d->wait = newRateInTics;
374     return *this;
375 }
376 
setColor(Vector3f const & newColor,int steps)377 FinaleTextWidget &FinaleTextWidget::setColor(Vector3f const &newColor, int steps)
378 {
379     AnimatorVector3_Set(*((animatorvector3_t *)d->color), newColor.x, newColor.y, newColor.z, steps);
380     d->pageColor = 0;
381     return *this;
382 }
383 
setAlpha(float alpha,int steps)384 FinaleTextWidget &FinaleTextWidget::setAlpha(float alpha, int steps)
385 {
386     Animator_Set(&d->color[CA], alpha, steps);
387     return *this;
388 }
389 
setColorAndAlpha(Vector4f const & newColorAndAlpha,int steps)390 FinaleTextWidget &FinaleTextWidget::setColorAndAlpha(Vector4f const &newColorAndAlpha, int steps)
391 {
392     AnimatorVector4_Set(d->color, newColorAndAlpha.x, newColorAndAlpha.y, newColorAndAlpha.z, newColorAndAlpha.w, steps);
393     d->pageColor = 0;
394     return *this;
395 }
396 
setPageColor(uint id)397 FinaleTextWidget &FinaleTextWidget::setPageColor(uint id)
398 {
399     d->pageColor = id;
400     return *this;
401 }
402 
setPageFont(uint id)403 FinaleTextWidget &FinaleTextWidget::setPageFont(uint id)
404 {
405     d->pageFont = id;
406     return *this;
407 }
408