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