1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2014-2018 - Jean-André Santoni
3 * Copyright (C) 2011-2018 - Daniel De Matteis
4 *
5 * RetroArch is free software: you can redistribute it and/or modify it under the terms
6 * of the GNU General Public License as published by the Free Software Found-
7 * ation, either version 3 of the License, or (at your option) any later version.
8 *
9 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with RetroArch.
14 * If not, see <http://www.gnu.org/licenses/>.
15 */
16
17 #include <math.h>
18 #include <string.h>
19
20 #include <compat/strl.h>
21 #include <encodings/utf.h>
22 #include <retro_math.h>
23 #include <retro_miscellaneous.h>
24 #include <string/stdstring.h>
25 #include <features/features_cpu.h>
26 #include <lists/string_list.h>
27 #include <array/rbuf.h>
28
29 #include "gfx_animation.h"
30 #include "../performance_counters.h"
31
32 #define TICKER_SPEED 333333
33 #define TICKER_SLOW_SPEED 1666666
34
35 /* Pixel ticker nominally increases by one after each
36 * TICKER_PIXEL_PERIOD ms (actual increase depends upon
37 * ticker speed setting and display resolution)
38 *
39 * Formula is: (1.0f / 60.0f) * 1000.0f
40 * */
41 #define TICKER_PIXEL_PERIOD (16.666666666666668f)
42
43 /* Mean human reading speed for all western languages,
44 * characters per minute */
45 #define TICKER_CPM 1000.0f
46 /* Base time for which a line should be shown, in us */
47 #define TICKER_LINE_DURATION_US(line_len) ((line_len * 60.0f * 1000.0f * 1000.0f) / TICKER_CPM)
48 /* Base time for which a line should be shown, in ms */
49 #define TICKER_LINE_DURATION_MS(line_len) ((line_len * 60.0f * 1000.0f) / TICKER_CPM)
50 /* Ticker updates (nominally) once every TICKER_SPEED us
51 * > Base number of ticks for which line should be shown */
52 #define TICKER_LINE_DISPLAY_TICKS(line_len) ((size_t)(TICKER_LINE_DURATION_US(line_len) / (float)TICKER_SPEED))
53 /* Smooth ticker updates (nominally) once every TICKER_PIXEL_PERIOD ms
54 * > Base number of ticks for which text should scroll
55 * from one line to the next */
56 #define TICKER_LINE_SMOOTH_SCROLL_TICKS(line_len) ((size_t)(TICKER_LINE_DURATION_MS(line_len) / TICKER_PIXEL_PERIOD))
57
58 /* from https://github.com/kikito/tween.lua/blob/master/tween.lua */
59
easing_linear(float t,float b,float c,float d)60 static float easing_linear(float t, float b, float c, float d)
61 {
62 return c * t / d + b;
63 }
64
easing_in_out_quad(float t,float b,float c,float d)65 static float easing_in_out_quad(float t, float b, float c, float d)
66 {
67 t = t / d * 2;
68 if (t < 1)
69 return c / 2 * pow(t, 2) + b;
70 return -c / 2 * ((t - 1) * (t - 3) - 1) + b;
71 }
72
easing_in_quad(float t,float b,float c,float d)73 static float easing_in_quad(float t, float b, float c, float d)
74 {
75 return c * pow(t / d, 2) + b;
76 }
77
easing_out_quad(float t,float b,float c,float d)78 static float easing_out_quad(float t, float b, float c, float d)
79 {
80 t = t / d;
81 return -c * t * (t - 2) + b;
82 }
83
easing_out_in_quad(float t,float b,float c,float d)84 static float easing_out_in_quad(float t, float b, float c, float d)
85 {
86 if (t < d / 2)
87 return easing_out_quad(t * 2, b, c / 2, d);
88 return easing_in_quad((t * 2) - d, b + c / 2, c / 2, d);
89 }
90
easing_in_cubic(float t,float b,float c,float d)91 static float easing_in_cubic(float t, float b, float c, float d)
92 {
93 return c * pow(t / d, 3) + b;
94 }
95
easing_out_cubic(float t,float b,float c,float d)96 static float easing_out_cubic(float t, float b, float c, float d)
97 {
98 return c * (pow(t / d - 1, 3) + 1) + b;
99 }
100
easing_in_out_cubic(float t,float b,float c,float d)101 static float easing_in_out_cubic(float t, float b, float c, float d)
102 {
103 t = t / d * 2;
104 if (t < 1)
105 return c / 2 * t * t * t + b;
106 t = t - 2;
107 return c / 2 * (t * t * t + 2) + b;
108 }
109
easing_out_in_cubic(float t,float b,float c,float d)110 static float easing_out_in_cubic(float t, float b, float c, float d)
111 {
112 if (t < d / 2)
113 return easing_out_cubic(t * 2, b, c / 2, d);
114 return easing_in_cubic((t * 2) - d, b + c / 2, c / 2, d);
115 }
116
easing_in_quart(float t,float b,float c,float d)117 static float easing_in_quart(float t, float b, float c, float d)
118 {
119 return c * pow(t / d, 4) + b;
120 }
121
easing_out_quart(float t,float b,float c,float d)122 static float easing_out_quart(float t, float b, float c, float d)
123 {
124 return -c * (pow(t / d - 1, 4) - 1) + b;
125 }
126
easing_in_out_quart(float t,float b,float c,float d)127 static float easing_in_out_quart(float t, float b, float c, float d)
128 {
129 t = t / d * 2;
130 if (t < 1)
131 return c / 2 * pow(t, 4) + b;
132 return -c / 2 * (pow(t - 2, 4) - 2) + b;
133 }
134
easing_out_in_quart(float t,float b,float c,float d)135 static float easing_out_in_quart(float t, float b, float c, float d)
136 {
137 if (t < d / 2)
138 return easing_out_quart(t * 2, b, c / 2, d);
139 return easing_in_quart((t * 2) - d, b + c / 2, c / 2, d);
140 }
141
easing_in_quint(float t,float b,float c,float d)142 static float easing_in_quint(float t, float b, float c, float d)
143 {
144 return c * pow(t / d, 5) + b;
145 }
146
easing_out_quint(float t,float b,float c,float d)147 static float easing_out_quint(float t, float b, float c, float d)
148 {
149 return c * (pow(t / d - 1, 5) + 1) + b;
150 }
151
easing_in_out_quint(float t,float b,float c,float d)152 static float easing_in_out_quint(float t, float b, float c, float d)
153 {
154 t = t / d * 2;
155 if (t < 1)
156 return c / 2 * pow(t, 5) + b;
157 return c / 2 * (pow(t - 2, 5) + 2) + b;
158 }
159
easing_out_in_quint(float t,float b,float c,float d)160 static float easing_out_in_quint(float t, float b, float c, float d)
161 {
162 if (t < d / 2)
163 return easing_out_quint(t * 2, b, c / 2, d);
164 return easing_in_quint((t * 2) - d, b + c / 2, c / 2, d);
165 }
166
easing_in_sine(float t,float b,float c,float d)167 static float easing_in_sine(float t, float b, float c, float d)
168 {
169 return -c * cos(t / d * (M_PI / 2)) + c + b;
170 }
171
easing_out_sine(float t,float b,float c,float d)172 static float easing_out_sine(float t, float b, float c, float d)
173 {
174 return c * sin(t / d * (M_PI / 2)) + b;
175 }
176
easing_in_out_sine(float t,float b,float c,float d)177 static float easing_in_out_sine(float t, float b, float c, float d)
178 {
179 return -c / 2 * (cos(M_PI * t / d) - 1) + b;
180 }
181
easing_out_in_sine(float t,float b,float c,float d)182 static float easing_out_in_sine(float t, float b, float c, float d)
183 {
184 if (t < d / 2)
185 return easing_out_sine(t * 2, b, c / 2, d);
186 return easing_in_sine((t * 2) -d, b + c / 2, c / 2, d);
187 }
188
easing_in_expo(float t,float b,float c,float d)189 static float easing_in_expo(float t, float b, float c, float d)
190 {
191 if (t == 0)
192 return b;
193 return c * powf(2, 10 * (t / d - 1)) + b - c * 0.001;
194 }
195
easing_out_expo(float t,float b,float c,float d)196 static float easing_out_expo(float t, float b, float c, float d)
197 {
198 if (t == d)
199 return b + c;
200 return c * 1.001 * (-powf(2, -10 * t / d) + 1) + b;
201 }
202
easing_in_out_expo(float t,float b,float c,float d)203 static float easing_in_out_expo(float t, float b, float c, float d)
204 {
205 if (t == 0)
206 return b;
207 if (t == d)
208 return b + c;
209 t = t / d * 2;
210 if (t < 1)
211 return c / 2 * powf(2, 10 * (t - 1)) + b - c * 0.0005;
212 return c / 2 * 1.0005 * (-powf(2, -10 * (t - 1)) + 2) + b;
213 }
214
easing_out_in_expo(float t,float b,float c,float d)215 static float easing_out_in_expo(float t, float b, float c, float d)
216 {
217 if (t < d / 2)
218 return easing_out_expo(t * 2, b, c / 2, d);
219 return easing_in_expo((t * 2) - d, b + c / 2, c / 2, d);
220 }
221
easing_in_circ(float t,float b,float c,float d)222 static float easing_in_circ(float t, float b, float c, float d)
223 {
224 return(-c * (sqrt(1 - powf(t / d, 2)) - 1) + b);
225 }
226
easing_out_circ(float t,float b,float c,float d)227 static float easing_out_circ(float t, float b, float c, float d)
228 {
229 return(c * sqrt(1 - powf(t / d - 1, 2)) + b);
230 }
231
easing_in_out_circ(float t,float b,float c,float d)232 static float easing_in_out_circ(float t, float b, float c, float d)
233 {
234 t = t / d * 2;
235 if (t < 1)
236 return -c / 2 * (sqrt(1 - t * t) - 1) + b;
237 t = t - 2;
238 return c / 2 * (sqrt(1 - t * t) + 1) + b;
239 }
240
easing_out_in_circ(float t,float b,float c,float d)241 static float easing_out_in_circ(float t, float b, float c, float d)
242 {
243 if (t < d / 2)
244 return easing_out_circ(t * 2, b, c / 2, d);
245 return easing_in_circ((t * 2) - d, b + c / 2, c / 2, d);
246 }
247
easing_out_bounce(float t,float b,float c,float d)248 static float easing_out_bounce(float t, float b, float c, float d)
249 {
250 t = t / d;
251 if (t < 1 / 2.75)
252 return c * (7.5625 * t * t) + b;
253 if (t < 2 / 2.75)
254 {
255 t = t - (1.5 / 2.75);
256 return c * (7.5625 * t * t + 0.75) + b;
257 }
258 else if (t < 2.5 / 2.75)
259 {
260 t = t - (2.25 / 2.75);
261 return c * (7.5625 * t * t + 0.9375) + b;
262 }
263 t = t - (2.625 / 2.75);
264 return c * (7.5625 * t * t + 0.984375) + b;
265 }
266
easing_in_bounce(float t,float b,float c,float d)267 static float easing_in_bounce(float t, float b, float c, float d)
268 {
269 return c - easing_out_bounce(d - t, 0, c, d) + b;
270 }
271
easing_in_out_bounce(float t,float b,float c,float d)272 static float easing_in_out_bounce(float t, float b, float c, float d)
273 {
274 if (t < d / 2)
275 return easing_in_bounce(t * 2, 0, c, d) * 0.5 + b;
276 return easing_out_bounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b;
277 }
278
easing_out_in_bounce(float t,float b,float c,float d)279 static float easing_out_in_bounce(float t, float b, float c, float d)
280 {
281 if (t < d / 2)
282 return easing_out_bounce(t * 2, b, c / 2, d);
283 return easing_in_bounce((t * 2) - d, b + c / 2, c / 2, d);
284 }
285
gfx_animation_ticker_generic(uint64_t idx,size_t old_width)286 static size_t gfx_animation_ticker_generic(uint64_t idx,
287 size_t old_width)
288 {
289 const int phase_left_stop = 2;
290 int ticker_period = (int)(2 * old_width + 4);
291 int phase = idx % ticker_period;
292
293 int phase_left_moving = (int)(phase_left_stop + old_width);
294 int phase_right_stop = phase_left_moving + 2;
295
296 int left_offset = phase - phase_left_stop;
297 int right_offset = (int)(old_width - (phase - phase_right_stop));
298
299 if (phase < phase_left_stop)
300 return 0;
301 else if (phase < phase_left_moving)
302 return left_offset;
303 else if (phase < phase_right_stop)
304 return old_width;
305 return right_offset;
306 }
307
gfx_animation_ticker_loop(uint64_t idx,size_t max_width,size_t str_width,size_t spacer_width,size_t * offset1,size_t * width1,size_t * offset2,size_t * width2,size_t * offset3,size_t * width3)308 static void gfx_animation_ticker_loop(uint64_t idx,
309 size_t max_width, size_t str_width, size_t spacer_width,
310 size_t *offset1, size_t *width1,
311 size_t *offset2, size_t *width2,
312 size_t *offset3, size_t *width3)
313 {
314 int ticker_period = (int)(str_width + spacer_width);
315 int phase = idx % ticker_period;
316
317 /* Output offsets/widths are unsigned size_t, but it's
318 * easier to perform the required calculations with ints,
319 * so create some temporary variables... */
320 /* Looping text is composed of up to three strings,
321 * where string 1 and 2 are different regions of the
322 * source text and string 2 is a spacer:
323 *
324 * |-----max_width-----|
325 * [string 1][string 2][string 3]
326 *
327 * The following implementation could probably be optimised,
328 * but any performance gains would be trivial compared with
329 * all the string manipulation that has to happen afterwards...
330 */
331
332 /* String 1 */
333 int offset = (phase < (int)str_width) ? phase : 0;
334 int width = (int)(str_width - phase);
335 width = (width < 0) ? 0 : width;
336 width = (width > (int)max_width) ? (int)max_width : width;
337
338 *offset1 = offset;
339 *width1 = width;
340
341 /* String 2 */
342 offset = (int)(phase - str_width);
343 offset = offset < 0 ? 0 : offset;
344 width = (int)(max_width - *width1);
345 width = (width > (int)spacer_width) ? (int)spacer_width : width;
346 width = width - offset;
347
348 *offset2 = offset;
349 *width2 = width;
350
351 /* String 3 */
352 width = (int)(max_width - (*width1 + *width2));
353 width = width < 0 ? 0 : width;
354
355 /* Note: offset is always zero here so offset3 is
356 * unnecessary - but include it anyway to preserve
357 * symmetry... */
358 *offset3 = 0;
359 *width3 = width;
360 }
361
get_ticker_smooth_generic_scroll_offset(uint64_t idx,unsigned str_width,unsigned field_width)362 static unsigned get_ticker_smooth_generic_scroll_offset(
363 uint64_t idx, unsigned str_width, unsigned field_width)
364 {
365 unsigned scroll_width = str_width - field_width;
366 unsigned pause_duration = 32;
367 unsigned ticker_period = 2 * (scroll_width + pause_duration);
368 unsigned phase = idx % ticker_period;
369
370 /* Determine scroll offset */
371 if (phase < pause_duration)
372 return 0;
373 else if (phase < ticker_period >> 1)
374 return (phase - pause_duration);
375 else if (phase < (ticker_period >> 1) + pause_duration)
376 return ((ticker_period - (2 * pause_duration)) >> 1);
377
378 return (ticker_period - phase);
379 }
380
381 /* 'Fixed width' font version of ticker_smooth_scan_characters() */
ticker_smooth_scan_string_fw(size_t num_chars,unsigned glyph_width,unsigned field_width,unsigned scroll_offset,unsigned * char_offset,unsigned * num_chars_to_copy,unsigned * x_offset)382 static void ticker_smooth_scan_string_fw(
383 size_t num_chars, unsigned glyph_width,
384 unsigned field_width, unsigned scroll_offset,
385 unsigned *char_offset, unsigned *num_chars_to_copy,
386 unsigned *x_offset)
387 {
388 unsigned chars_remaining = 0;
389
390 /* Initialise output variables to 'sane' values */
391 *char_offset = 0;
392 *num_chars_to_copy = 0;
393 *x_offset = 0;
394
395 /* Determine index of first character to copy */
396 if (scroll_offset > 0)
397 {
398 *char_offset = (scroll_offset / glyph_width) + 1;
399 *x_offset = glyph_width - (scroll_offset % glyph_width);
400 }
401
402 /* Determine number of characters remaining in
403 * string once offset has been subtracted */
404 if (*char_offset < num_chars)
405 chars_remaining = num_chars - *char_offset;
406
407 /* Determine number of characters to copy */
408 if ((chars_remaining > 0) && (field_width > *x_offset))
409 {
410 *num_chars_to_copy = (field_width - *x_offset) / glyph_width;
411 if (*num_chars_to_copy > chars_remaining)
412 *num_chars_to_copy = chars_remaining;
413 }
414 }
415
416 /* 'Fixed width' font version of gfx_animation_ticker_smooth_generic() */
gfx_animation_ticker_smooth_generic_fw(uint64_t idx,unsigned str_width,size_t num_chars,unsigned glyph_width,unsigned field_width,unsigned * char_offset,unsigned * num_chars_to_copy,unsigned * x_offset)417 static void gfx_animation_ticker_smooth_generic_fw(uint64_t idx,
418 unsigned str_width, size_t num_chars,
419 unsigned glyph_width, unsigned field_width,
420 unsigned *char_offset, unsigned *num_chars_to_copy, unsigned *x_offset)
421 {
422 unsigned scroll_offset = get_ticker_smooth_generic_scroll_offset(
423 idx, str_width, field_width);
424
425 /* Initialise output variables to 'sane' values */
426 *char_offset = 0;
427 *num_chars_to_copy = 0;
428 *x_offset = 0;
429
430 /* Sanity check */
431 if (num_chars < 1)
432 return;
433
434 ticker_smooth_scan_string_fw(
435 num_chars, glyph_width, field_width, scroll_offset,
436 char_offset, num_chars_to_copy, x_offset);
437 }
438
439 /* 'Fixed width' font version of gfx_animation_ticker_smooth_loop() */
gfx_animation_ticker_smooth_loop_fw(uint64_t idx,unsigned str_width,size_t num_chars,unsigned spacer_width,size_t num_spacer_chars,unsigned glyph_width,unsigned field_width,unsigned * char_offset1,unsigned * num_chars_to_copy1,unsigned * char_offset2,unsigned * num_chars_to_copy2,unsigned * char_offset3,unsigned * num_chars_to_copy3,unsigned * x_offset)440 static void gfx_animation_ticker_smooth_loop_fw(uint64_t idx,
441 unsigned str_width, size_t num_chars,
442 unsigned spacer_width, size_t num_spacer_chars,
443 unsigned glyph_width, unsigned field_width,
444 unsigned *char_offset1, unsigned *num_chars_to_copy1,
445 unsigned *char_offset2, unsigned *num_chars_to_copy2,
446 unsigned *char_offset3, unsigned *num_chars_to_copy3,
447 unsigned *x_offset)
448 {
449 unsigned ticker_period = str_width + spacer_width;
450 unsigned phase = idx % ticker_period;
451
452 unsigned remaining_width = field_width;
453
454 /* Initialise output variables to 'sane' values */
455 *char_offset1 = 0;
456 *num_chars_to_copy1 = 0;
457 *char_offset2 = 0;
458 *num_chars_to_copy2 = 0;
459 *char_offset3 = 0;
460 *num_chars_to_copy3 = 0;
461 *x_offset = 0;
462
463 /* Sanity check */
464 if ((num_chars < 1) || (num_spacer_chars < 1))
465 return;
466
467 /* Looping text is composed of up to three strings,
468 * where string 1 and 2 are different regions of the
469 * source text and string 2 is a spacer:
470 *
471 * |----field_width----|
472 * [string 1][string 2][string 3]
473 */
474
475 /* String 1 */
476 if (phase < str_width)
477 {
478 unsigned scroll_offset = phase;
479
480 ticker_smooth_scan_string_fw(
481 num_chars, glyph_width, remaining_width, scroll_offset,
482 char_offset1, num_chars_to_copy1, x_offset);
483
484 /* Update remaining width
485 * Note: We can avoid all the display_width shenanigans
486 * here (c.f. gfx_animation_ticker_smooth_loop()) because
487 * the font width is constant - i.e. we don't have to wrangle
488 * out the width of the last 'non-copied' character since it
489 * is known a priori, so we can just subtract the string width
490 * + offset here, and perform an 'if (remaining_width > glyph_width)'
491 * for strings 2 and 3 */
492 remaining_width -= (*x_offset + (*num_chars_to_copy1 * glyph_width));
493 }
494
495 /* String 2 */
496 if (remaining_width > glyph_width)
497 {
498 unsigned scroll_offset = 0;
499 unsigned x_offset2 = 0;
500
501 /* Check whether we've passed the end of string 1 */
502 if (phase > str_width)
503 scroll_offset = phase - str_width;
504
505 ticker_smooth_scan_string_fw(
506 num_spacer_chars, glyph_width, remaining_width, scroll_offset,
507 char_offset2, num_chars_to_copy2, &x_offset2);
508
509 /* > Update remaining width */
510 remaining_width -= (x_offset2 + (*num_chars_to_copy2 * glyph_width));
511
512 /* If scroll_offset is greater than zero, it means
513 * string 2 is the first string to be displayed
514 * > ticker x offset is therefore string 2's offset */
515 if (scroll_offset > 0)
516 *x_offset = x_offset2;
517 }
518
519 /* String 3 */
520 if (remaining_width > glyph_width)
521 {
522 /* String 3 is only shown when string 2 is shown,
523 * so we can take some shortcuts... */
524 *char_offset3 = 0;
525
526 /* Determine number of characters to copy */
527 *num_chars_to_copy3 = remaining_width / glyph_width;
528 if (*num_chars_to_copy3 > num_chars)
529 *num_chars_to_copy3 = num_chars;
530 }
531 }
532
ticker_smooth_scan_characters(const unsigned * char_widths,size_t num_chars,unsigned field_width,unsigned scroll_offset,unsigned * char_offset,unsigned * num_chars_to_copy,unsigned * x_offset,unsigned * str_width,unsigned * display_width)533 static void ticker_smooth_scan_characters(
534 const unsigned *char_widths, size_t num_chars,
535 unsigned field_width, unsigned scroll_offset,
536 unsigned *char_offset, unsigned *num_chars_to_copy,
537 unsigned *x_offset, unsigned *str_width,
538 unsigned *display_width)
539 {
540 unsigned i;
541 unsigned text_width = 0;
542 unsigned scroll_pos = scroll_offset;
543 bool deferred_str_width = true;
544
545 /* Initialise output variables to 'sane' values */
546 *char_offset = 0;
547 *num_chars_to_copy = 0;
548 *x_offset = 0;
549 if (str_width)
550 *str_width = 0;
551 if (display_width)
552 *display_width = 0;
553
554 /* Determine index of first character to copy */
555 if (scroll_pos > 0)
556 {
557 for (i = 0; i < num_chars; i++)
558 {
559 if (scroll_pos > char_widths[i])
560 scroll_pos -= char_widths[i];
561 else
562 {
563 /* Note: It's okay for char_offset to go out
564 * of range here (num_chars_to_copy will be zero
565 * in this case) */
566 *char_offset = i + 1;
567 *x_offset = char_widths[i] - scroll_pos;
568 break;
569 }
570 }
571 }
572
573 /* Determine number of characters to copy */
574 for (i = *char_offset; i < num_chars; i++)
575 {
576 text_width += char_widths[i];
577
578 if (*x_offset + text_width <= field_width)
579 (*num_chars_to_copy)++;
580 else
581 {
582 /* Get actual width of resultant string
583 * (excluding x offset + end padding)
584 * Note that this is only set if we exceed the
585 * field width - if all characters up to the end
586 * of the string are copied... */
587 if (str_width)
588 {
589 deferred_str_width = false;
590 *str_width = text_width - char_widths[i];
591 }
592 break;
593 }
594 }
595
596 /* ...then we have to update str_width here instead */
597 if (str_width)
598 if (deferred_str_width)
599 *str_width = text_width;
600
601 /* Get total display width of resultant string
602 * (x offset + text width + end padding) */
603 if (display_width)
604 {
605 *display_width = *x_offset + text_width;
606 *display_width = (*display_width > field_width) ? field_width : *display_width;
607 }
608 }
609
gfx_animation_ticker_smooth_generic(uint64_t idx,const unsigned * char_widths,size_t num_chars,unsigned str_width,unsigned field_width,unsigned * char_offset,unsigned * num_chars_to_copy,unsigned * x_offset,unsigned * dst_str_width)610 static void gfx_animation_ticker_smooth_generic(uint64_t idx,
611 const unsigned *char_widths, size_t num_chars,
612 unsigned str_width, unsigned field_width,
613 unsigned *char_offset, unsigned *num_chars_to_copy,
614 unsigned *x_offset, unsigned *dst_str_width)
615 {
616 unsigned scroll_offset = get_ticker_smooth_generic_scroll_offset(
617 idx, str_width, field_width);
618
619 /* Initialise output variables to 'sane' values */
620 *char_offset = 0;
621 *num_chars_to_copy = 0;
622 *x_offset = 0;
623 if (dst_str_width)
624 *dst_str_width = 0;
625
626 /* Sanity check */
627 if (num_chars < 1)
628 return;
629
630 ticker_smooth_scan_characters(
631 char_widths, num_chars, field_width, scroll_offset,
632 char_offset, num_chars_to_copy, x_offset, dst_str_width, NULL);
633 }
634
gfx_animation_ticker_smooth_loop(uint64_t idx,const unsigned * char_widths,size_t num_chars,const unsigned * spacer_widths,size_t num_spacer_chars,unsigned str_width,unsigned spacer_width,unsigned field_width,unsigned * char_offset1,unsigned * num_chars_to_copy1,unsigned * char_offset2,unsigned * num_chars_to_copy2,unsigned * char_offset3,unsigned * num_chars_to_copy3,unsigned * x_offset,unsigned * dst_str_width)635 static void gfx_animation_ticker_smooth_loop(uint64_t idx,
636 const unsigned *char_widths, size_t num_chars,
637 const unsigned *spacer_widths, size_t num_spacer_chars,
638 unsigned str_width, unsigned spacer_width, unsigned field_width,
639 unsigned *char_offset1, unsigned *num_chars_to_copy1,
640 unsigned *char_offset2, unsigned *num_chars_to_copy2,
641 unsigned *char_offset3, unsigned *num_chars_to_copy3,
642 unsigned *x_offset, unsigned *dst_str_width)
643
644 {
645 unsigned ticker_period = str_width + spacer_width;
646 unsigned phase = idx % ticker_period;
647
648 unsigned remaining_width = field_width;
649
650 /* Initialise output variables to 'sane' values */
651 *char_offset1 = 0;
652 *num_chars_to_copy1 = 0;
653 *char_offset2 = 0;
654 *num_chars_to_copy2 = 0;
655 *char_offset3 = 0;
656 *num_chars_to_copy3 = 0;
657 *x_offset = 0;
658 if (dst_str_width)
659 *dst_str_width = 0;
660
661 /* Sanity check */
662 if ((num_chars < 1) || (num_spacer_chars < 1))
663 return;
664
665 /* Looping text is composed of up to three strings,
666 * where string 1 and 2 are different regions of the
667 * source text and string 2 is a spacer:
668 *
669 * |----field_width----|
670 * [string 1][string 2][string 3]
671 */
672
673 /* String 1 */
674 if (phase < str_width)
675 {
676 unsigned scroll_offset = phase;
677 unsigned display_width = 0;
678 unsigned str1_width = 0;
679
680 ticker_smooth_scan_characters(
681 char_widths, num_chars, remaining_width, scroll_offset,
682 char_offset1, num_chars_to_copy1, x_offset, &str1_width, &display_width);
683
684 /* Update remaining width */
685 remaining_width -= display_width;
686
687 /* Update dst_str_width */
688 if (dst_str_width)
689 *dst_str_width += str1_width;
690 }
691
692 /* String 2 */
693 if (remaining_width > 0)
694 {
695 unsigned scroll_offset = 0;
696 unsigned display_width = 0;
697 unsigned str2_width = 0;
698 unsigned x_offset2 = 0;
699
700 /* Check whether we've passed the end of string 1 */
701 if (phase > str_width)
702 scroll_offset = phase - str_width;
703
704 ticker_smooth_scan_characters(
705 spacer_widths, num_spacer_chars, remaining_width, scroll_offset,
706 char_offset2, num_chars_to_copy2, &x_offset2, &str2_width, &display_width);
707
708 /* > Update remaining width */
709 remaining_width -= display_width;
710
711 /* Update dst_str_width */
712 if (dst_str_width)
713 *dst_str_width += str2_width;
714
715 /* If scroll_offset is greater than zero, it means
716 * string 2 is the first string to be displayed
717 * > ticker x offset is therefore string 2's offset */
718 if (scroll_offset > 0)
719 *x_offset = x_offset2;
720 }
721
722 /* String 3 */
723 if (remaining_width > 0)
724 {
725 /* String 3 is only shown when string 2 is shown,
726 * so we can take some shortcuts... */
727 unsigned i;
728 unsigned text_width = 0;
729 *char_offset3 = 0;
730
731 /* Determine number of characters to copy */
732 for (i = 0; i < num_chars; i++)
733 {
734 text_width += char_widths[i];
735
736 if (text_width <= remaining_width)
737 (*num_chars_to_copy3)++;
738 else
739 {
740 /* Update dst_str_width */
741 if (dst_str_width)
742 *dst_str_width += text_width - char_widths[i];
743 break;
744 }
745 }
746 }
747 }
748
gfx_animation_line_ticker_generic(uint64_t idx,size_t line_len,size_t max_lines,size_t num_lines)749 static size_t gfx_animation_line_ticker_generic(uint64_t idx,
750 size_t line_len, size_t max_lines, size_t num_lines)
751 {
752 size_t line_ticks = TICKER_LINE_DISPLAY_TICKS(line_len);
753 /* Note: This function is only called if num_lines > max_lines */
754 size_t excess_lines = num_lines - max_lines;
755 /* Ticker will pause for one line duration when the first
756 * or last line is reached (this is mostly required for the
757 * case where num_lines == (max_lines + 1), since otherwise
758 * the text flicks rapidly up and down in disconcerting
759 * fashion...) */
760 size_t ticker_period = (excess_lines * 2) + 2;
761 size_t phase = (idx / line_ticks) % ticker_period;
762
763 /* Pause on first line */
764 if (phase > 0)
765 phase--;
766 /* Pause on last line */
767 if (phase > excess_lines)
768 phase--;
769
770 /* Lines scrolling upwards */
771 if (phase <= excess_lines)
772 return phase;
773 /* Lines scrolling downwards */
774 return (excess_lines * 2) - phase;
775 }
776
gfx_animation_line_ticker_loop(uint64_t idx,size_t line_len,size_t num_lines)777 static size_t gfx_animation_line_ticker_loop(uint64_t idx,
778 size_t line_len, size_t num_lines)
779 {
780 size_t line_ticks = TICKER_LINE_DISPLAY_TICKS(line_len);
781 size_t ticker_period = num_lines + 1;
782 size_t phase = (idx / line_ticks) % ticker_period;
783 /* In this case, line_offset is simply equal to the phase */
784 return phase;
785 }
786
set_line_smooth_fade_parameters(bool scroll_up,size_t scroll_ticks,size_t line_phase,size_t line_height,size_t num_lines,size_t num_display_lines,size_t line_offset,float y_offset,size_t * top_fade_line_offset,float * top_fade_y_offset,float * top_fade_alpha,size_t * bottom_fade_line_offset,float * bottom_fade_y_offset,float * bottom_fade_alpha)787 static void set_line_smooth_fade_parameters(
788 bool scroll_up,
789 size_t scroll_ticks, size_t line_phase, size_t line_height,
790 size_t num_lines, size_t num_display_lines, size_t line_offset,
791 float y_offset,
792 size_t *top_fade_line_offset,
793 float *top_fade_y_offset, float *top_fade_alpha,
794 size_t *bottom_fade_line_offset,
795 float *bottom_fade_y_offset, float *bottom_fade_alpha)
796 {
797 /* When a line fades out, alpha transitions from
798 * 1 to 0 over the course of one half of the
799 * scrolling line height. When a line fades in,
800 * it's the other way around */
801 float fade_out_alpha = ((float)scroll_ticks - ((float)line_phase * 2.0f)) / (float)scroll_ticks;
802 float fade_in_alpha = -1.0f * fade_out_alpha;
803 fade_out_alpha = (fade_out_alpha < 0.0f) ? 0.0f : fade_out_alpha;
804 fade_in_alpha = (fade_in_alpha < 0.0f) ? 0.0f : fade_in_alpha;
805
806 *top_fade_line_offset = (line_offset > 0) ? line_offset - 1 : num_lines;
807 *top_fade_y_offset = y_offset - (float)line_height;
808 if (scroll_up)
809 {
810 *top_fade_alpha = fade_out_alpha;
811 *bottom_fade_alpha = fade_in_alpha;
812 }
813 else
814 {
815 *top_fade_alpha = fade_in_alpha;
816 *bottom_fade_alpha = fade_out_alpha;
817 }
818 *bottom_fade_line_offset = line_offset + num_display_lines;
819 *bottom_fade_y_offset = y_offset + (float)(line_height * num_display_lines);
820 }
821
set_line_smooth_fade_parameters_default(size_t * top_fade_line_offset,float * top_fade_y_offset,float * top_fade_alpha,size_t * bottom_fade_line_offset,float * bottom_fade_y_offset,float * bottom_fade_alpha)822 static void set_line_smooth_fade_parameters_default(
823 size_t *top_fade_line_offset, float *top_fade_y_offset, float *top_fade_alpha,
824 size_t *bottom_fade_line_offset, float *bottom_fade_y_offset, float *bottom_fade_alpha)
825 {
826 *top_fade_line_offset = 0;
827 *top_fade_y_offset = 0.0f;
828 *top_fade_alpha = 0.0f;
829
830 *bottom_fade_line_offset = 0;
831 *bottom_fade_y_offset = 0.0f;
832 *bottom_fade_alpha = 0.0f;
833 }
834
gfx_animation_line_ticker_smooth_generic(uint64_t idx,bool fade_enabled,size_t line_len,size_t line_height,size_t max_display_lines,size_t num_lines,size_t * num_display_lines,size_t * line_offset,float * y_offset,bool * fade_active,size_t * top_fade_line_offset,float * top_fade_y_offset,float * top_fade_alpha,size_t * bottom_fade_line_offset,float * bottom_fade_y_offset,float * bottom_fade_alpha)835 static void gfx_animation_line_ticker_smooth_generic(uint64_t idx,
836 bool fade_enabled, size_t line_len, size_t line_height,
837 size_t max_display_lines, size_t num_lines,
838 size_t *num_display_lines, size_t *line_offset,
839 float *y_offset,
840 bool *fade_active,
841 size_t *top_fade_line_offset, float *top_fade_y_offset,
842 float *top_fade_alpha,
843 size_t *bottom_fade_line_offset, float *bottom_fade_y_offset,
844 float *bottom_fade_alpha)
845 {
846 size_t scroll_ticks = TICKER_LINE_SMOOTH_SCROLL_TICKS(line_len);
847 /* Note: This function is only called if num_lines > max_display_lines */
848 size_t excess_lines = num_lines - max_display_lines;
849 /* Ticker will pause for one line duration when the first
850 * or last line is reached */
851 size_t ticker_period = ((excess_lines * 2) + 2) * scroll_ticks;
852 size_t phase = idx % ticker_period;
853 size_t line_phase = 0;
854 bool pause = false;
855 bool scroll_up = true;
856
857 /* Pause on first line */
858 if (phase < scroll_ticks)
859 pause = true;
860 phase = (phase >= scroll_ticks) ? phase - scroll_ticks : 0;
861 /* Pause on last line and change direction */
862 if (phase >= excess_lines * scroll_ticks)
863 {
864 scroll_up = false;
865
866 if (phase < (excess_lines + 1) * scroll_ticks)
867 {
868 pause = true;
869 phase = 0;
870 }
871 else
872 phase -= (excess_lines + 1) * scroll_ticks;
873 }
874
875 line_phase = phase % scroll_ticks;
876
877 if (pause || (line_phase == 0))
878 {
879 /* Static display of max_display_lines
880 * (no animation) */
881 *num_display_lines = max_display_lines;
882 *y_offset = 0.0f;
883 *fade_active = false;
884
885 if (pause)
886 *line_offset = scroll_up ? 0 : excess_lines;
887 else
888 *line_offset = scroll_up ? (phase / scroll_ticks) : (excess_lines - (phase / scroll_ticks));
889 }
890 else
891 {
892 /* Scroll animation is active */
893 *num_display_lines = max_display_lines - 1;
894 *fade_active = fade_enabled;
895
896 if (scroll_up)
897 {
898 *line_offset = (phase / scroll_ticks) + 1;
899 *y_offset = (float)line_height * (float)(scroll_ticks - line_phase) / (float)scroll_ticks;
900 }
901 else
902 {
903 *line_offset = excess_lines - (phase / scroll_ticks);
904 *y_offset = (float)line_height * (1.0f - (float)(scroll_ticks - line_phase) / (float)scroll_ticks);
905 }
906
907 /* Set fade parameters if fade animation is active */
908 if (*fade_active)
909 set_line_smooth_fade_parameters(
910 scroll_up, scroll_ticks, line_phase, line_height,
911 num_lines, *num_display_lines, *line_offset, *y_offset,
912 top_fade_line_offset, top_fade_y_offset, top_fade_alpha,
913 bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha);
914 }
915
916 /* Set 'default' fade parameters if fade animation
917 * is inactive */
918 if (!*fade_active)
919 set_line_smooth_fade_parameters_default(
920 top_fade_line_offset, top_fade_y_offset, top_fade_alpha,
921 bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha);
922 }
923
gfx_animation_line_ticker_smooth_loop(uint64_t idx,bool fade_enabled,size_t line_len,size_t line_height,size_t max_display_lines,size_t num_lines,size_t * num_display_lines,size_t * line_offset,float * y_offset,bool * fade_active,size_t * top_fade_line_offset,float * top_fade_y_offset,float * top_fade_alpha,size_t * bottom_fade_line_offset,float * bottom_fade_y_offset,float * bottom_fade_alpha)924 static void gfx_animation_line_ticker_smooth_loop(uint64_t idx,
925 bool fade_enabled, size_t line_len, size_t line_height,
926 size_t max_display_lines, size_t num_lines,
927 size_t *num_display_lines, size_t *line_offset, float *y_offset,
928 bool *fade_active,
929 size_t *top_fade_line_offset, float *top_fade_y_offset, float *top_fade_alpha,
930 size_t *bottom_fade_line_offset, float *bottom_fade_y_offset, float *bottom_fade_alpha)
931 {
932 size_t scroll_ticks = TICKER_LINE_SMOOTH_SCROLL_TICKS(line_len);
933 size_t ticker_period = (num_lines + 1) * scroll_ticks;
934 size_t phase = idx % ticker_period;
935 size_t line_phase = phase % scroll_ticks;
936
937 *line_offset = phase / scroll_ticks;
938
939 if (line_phase == (scroll_ticks - 1))
940 {
941 /* Static display of max_display_lines
942 * (no animation) */
943 *num_display_lines = max_display_lines;
944 *fade_active = false;
945 }
946 else
947 {
948 *num_display_lines = max_display_lines - 1;
949 *fade_active = fade_enabled;
950 }
951
952 *y_offset = (float)line_height * (float)(scroll_ticks - line_phase) / (float)scroll_ticks;
953
954 /* Set fade parameters */
955 if (*fade_active)
956 set_line_smooth_fade_parameters(
957 true, scroll_ticks, line_phase, line_height,
958 num_lines, *num_display_lines, *line_offset, *y_offset,
959 top_fade_line_offset, top_fade_y_offset, top_fade_alpha,
960 bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha);
961 else
962 set_line_smooth_fade_parameters_default(
963 top_fade_line_offset, top_fade_y_offset, top_fade_alpha,
964 bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha);
965 }
966
gfx_delayed_animation_cb(void * userdata)967 static void gfx_delayed_animation_cb(void *userdata)
968 {
969 gfx_delayed_animation_t *delayed_animation =
970 (gfx_delayed_animation_t*) userdata;
971
972 gfx_animation_push(&delayed_animation->entry);
973
974 free(delayed_animation);
975 }
976
gfx_animation_push_delayed(unsigned delay,gfx_animation_ctx_entry_t * entry)977 void gfx_animation_push_delayed(
978 unsigned delay, gfx_animation_ctx_entry_t *entry)
979 {
980 gfx_timer_ctx_entry_t timer_entry;
981 gfx_delayed_animation_t *delayed_animation = (gfx_delayed_animation_t*)
982 malloc(sizeof(gfx_delayed_animation_t));
983
984 memcpy(&delayed_animation->entry, entry, sizeof(gfx_animation_ctx_entry_t));
985
986 timer_entry.cb = gfx_delayed_animation_cb;
987 timer_entry.duration = delay;
988 timer_entry.userdata = delayed_animation;
989
990 gfx_animation_timer_start(&delayed_animation->timer, &timer_entry);
991 }
992
gfx_animation_push(gfx_animation_ctx_entry_t * entry)993 bool gfx_animation_push(gfx_animation_ctx_entry_t *entry)
994 {
995 struct tween t;
996 gfx_animation_t *p_anim = anim_get_ptr();
997
998 t.duration = entry->duration;
999 t.running_since = 0;
1000 t.initial_value = *entry->subject;
1001 t.target_value = entry->target_value;
1002 t.subject = entry->subject;
1003 t.tag = entry->tag;
1004 t.cb = entry->cb;
1005 t.userdata = entry->userdata;
1006 t.easing = NULL;
1007 t.deleted = false;
1008
1009 switch (entry->easing_enum)
1010 {
1011 case EASING_LINEAR:
1012 t.easing = &easing_linear;
1013 break;
1014 /* Quad */
1015 case EASING_IN_QUAD:
1016 t.easing = &easing_in_quad;
1017 break;
1018 case EASING_OUT_QUAD:
1019 t.easing = &easing_out_quad;
1020 break;
1021 case EASING_IN_OUT_QUAD:
1022 t.easing = &easing_in_out_quad;
1023 break;
1024 case EASING_OUT_IN_QUAD:
1025 t.easing = &easing_out_in_quad;
1026 break;
1027 /* Cubic */
1028 case EASING_IN_CUBIC:
1029 t.easing = &easing_in_cubic;
1030 break;
1031 case EASING_OUT_CUBIC:
1032 t.easing = &easing_out_cubic;
1033 break;
1034 case EASING_IN_OUT_CUBIC:
1035 t.easing = &easing_in_out_cubic;
1036 break;
1037 case EASING_OUT_IN_CUBIC:
1038 t.easing = &easing_out_in_cubic;
1039 break;
1040 /* Quart */
1041 case EASING_IN_QUART:
1042 t.easing = &easing_in_quart;
1043 break;
1044 case EASING_OUT_QUART:
1045 t.easing = &easing_out_quart;
1046 break;
1047 case EASING_IN_OUT_QUART:
1048 t.easing = &easing_in_out_quart;
1049 break;
1050 case EASING_OUT_IN_QUART:
1051 t.easing = &easing_out_in_quart;
1052 break;
1053 /* Quint */
1054 case EASING_IN_QUINT:
1055 t.easing = &easing_in_quint;
1056 break;
1057 case EASING_OUT_QUINT:
1058 t.easing = &easing_out_quint;
1059 break;
1060 case EASING_IN_OUT_QUINT:
1061 t.easing = &easing_in_out_quint;
1062 break;
1063 case EASING_OUT_IN_QUINT:
1064 t.easing = &easing_out_in_quint;
1065 break;
1066 /* Sine */
1067 case EASING_IN_SINE:
1068 t.easing = &easing_in_sine;
1069 break;
1070 case EASING_OUT_SINE:
1071 t.easing = &easing_out_sine;
1072 break;
1073 case EASING_IN_OUT_SINE:
1074 t.easing = &easing_in_out_sine;
1075 break;
1076 case EASING_OUT_IN_SINE:
1077 t.easing = &easing_out_in_sine;
1078 break;
1079 /* Expo */
1080 case EASING_IN_EXPO:
1081 t.easing = &easing_in_expo;
1082 break;
1083 case EASING_OUT_EXPO:
1084 t.easing = &easing_out_expo;
1085 break;
1086 case EASING_IN_OUT_EXPO:
1087 t.easing = &easing_in_out_expo;
1088 break;
1089 case EASING_OUT_IN_EXPO:
1090 t.easing = &easing_out_in_expo;
1091 break;
1092 /* Circ */
1093 case EASING_IN_CIRC:
1094 t.easing = &easing_in_circ;
1095 break;
1096 case EASING_OUT_CIRC:
1097 t.easing = &easing_out_circ;
1098 break;
1099 case EASING_IN_OUT_CIRC:
1100 t.easing = &easing_in_out_circ;
1101 break;
1102 case EASING_OUT_IN_CIRC:
1103 t.easing = &easing_out_in_circ;
1104 break;
1105 /* Bounce */
1106 case EASING_IN_BOUNCE:
1107 t.easing = &easing_in_bounce;
1108 break;
1109 case EASING_OUT_BOUNCE:
1110 t.easing = &easing_out_bounce;
1111 break;
1112 case EASING_IN_OUT_BOUNCE:
1113 t.easing = &easing_in_out_bounce;
1114 break;
1115 case EASING_OUT_IN_BOUNCE:
1116 t.easing = &easing_out_in_bounce;
1117 break;
1118 default:
1119 break;
1120 }
1121
1122 /* ignore born dead tweens */
1123 if (!t.easing || t.duration == 0 || t.initial_value == t.target_value)
1124 return false;
1125
1126 if (p_anim->in_update)
1127 RBUF_PUSH(p_anim->pending, t);
1128 else
1129 RBUF_PUSH(p_anim->list, t);
1130
1131 return true;
1132 }
1133
gfx_animation_update(gfx_animation_t * p_anim,retro_time_t current_time,bool timedate_enable,float _ticker_speed,unsigned video_width,unsigned video_height)1134 bool gfx_animation_update(
1135 gfx_animation_t *p_anim,
1136 retro_time_t current_time,
1137 bool timedate_enable,
1138 float _ticker_speed,
1139 unsigned video_width,
1140 unsigned video_height)
1141 {
1142 unsigned i;
1143 const bool ticker_is_active = p_anim->ticker_is_active;
1144
1145 static retro_time_t last_clock_update = 0;
1146 static retro_time_t last_ticker_update = 0;
1147 static retro_time_t last_ticker_slow_update = 0;
1148
1149 /* Horizontal smooth ticker parameters */
1150 static float ticker_pixel_accumulator = 0.0f;
1151 unsigned ticker_pixel_accumulator_uint = 0;
1152 float ticker_pixel_increment = 0.0f;
1153
1154 /* Vertical (line) smooth ticker parameters */
1155 static float ticker_pixel_line_accumulator = 0.0f;
1156 unsigned ticker_pixel_line_accumulator_uint = 0;
1157 float ticker_pixel_line_increment = 0.0f;
1158
1159 /* Adjust ticker speed */
1160 float speed_factor =
1161 (_ticker_speed > 0.0001f) ? _ticker_speed : 1.0f;
1162 unsigned ticker_speed =
1163 (unsigned)(((float)TICKER_SPEED / speed_factor) + 0.5);
1164 unsigned ticker_slow_speed =
1165 (unsigned)(((float)TICKER_SLOW_SPEED / speed_factor) + 0.5);
1166
1167 /* Note: cur_time & old_time are in us (microseconds),
1168 * delta_time is in ms */
1169 p_anim->cur_time = current_time;
1170 p_anim->delta_time = (p_anim->old_time == 0)
1171 ? 0.0f
1172 : (float)(p_anim->cur_time - p_anim->old_time) / 1000.0f;
1173 p_anim->old_time = p_anim->cur_time;
1174
1175 if (((p_anim->cur_time - last_clock_update) > 1000000) /* 1000000 us == 1 second */
1176 && timedate_enable)
1177 {
1178 p_anim->animation_is_active = true;
1179 last_clock_update = p_anim->cur_time;
1180 }
1181
1182 if (ticker_is_active)
1183 {
1184 /* Update non-smooth ticker indices */
1185 if (p_anim->cur_time - last_ticker_update >= ticker_speed)
1186 {
1187 p_anim->ticker_idx++;
1188 last_ticker_update = p_anim->cur_time;
1189 }
1190
1191 if (p_anim->cur_time - last_ticker_slow_update >= ticker_slow_speed)
1192 {
1193 p_anim->ticker_slow_idx++;
1194 last_ticker_slow_update = p_anim->cur_time;
1195 }
1196
1197 /* Pixel tickers (horizontal + vertical/line) update
1198 * every frame (regardless of time delta), so require
1199 * special handling */
1200
1201 /* > Get base increment size (+1 every TICKER_PIXEL_PERIOD ms) */
1202 ticker_pixel_increment = p_anim->delta_time / TICKER_PIXEL_PERIOD;
1203
1204 /* > Apply ticker speed adjustment */
1205 ticker_pixel_increment *= speed_factor;
1206
1207 /* At this point we diverge:
1208 * > Vertical (line) ticker is based upon text
1209 * characteristics (number of characters per
1210 * line) - it is therefore independent of display
1211 * size/scaling, so speed-adjusted pixel increment
1212 * is used directly */
1213 ticker_pixel_line_increment = ticker_pixel_increment;
1214
1215 /* > Horizontal ticker is based upon physical line
1216 * width - it is therefore very much dependent upon
1217 * display size/scaling. Each menu driver is free
1218 * to handle video scaling as it pleases - a callback
1219 * function set by the menu driver is thus used to
1220 * perform menu-specific scaling adjustments */
1221 if (p_anim->updatetime_cb)
1222 p_anim->updatetime_cb(&ticker_pixel_increment,
1223 video_width, video_height);
1224
1225 /* > Update accumulators */
1226 ticker_pixel_accumulator += ticker_pixel_increment;
1227 ticker_pixel_accumulator_uint = (unsigned)ticker_pixel_accumulator;
1228
1229 ticker_pixel_line_accumulator += ticker_pixel_line_increment;
1230 ticker_pixel_line_accumulator_uint = (unsigned)ticker_pixel_line_accumulator;
1231
1232 /* > Check whether we've accumulated enough
1233 * for an idx update */
1234 if (ticker_pixel_accumulator_uint > 0)
1235 {
1236 p_anim->ticker_pixel_idx += ticker_pixel_accumulator_uint;
1237 ticker_pixel_accumulator -= (float)ticker_pixel_accumulator_uint;
1238 }
1239
1240 if (ticker_pixel_accumulator_uint > 0)
1241 {
1242 p_anim->ticker_pixel_line_idx += ticker_pixel_line_accumulator_uint;
1243 ticker_pixel_line_accumulator -= (float)ticker_pixel_line_accumulator_uint;
1244 }
1245 }
1246
1247 p_anim->in_update = true;
1248 p_anim->pending_deletes = false;
1249
1250 for (i = 0; i < RBUF_LEN(p_anim->list); i++)
1251 {
1252 struct tween *tween = &p_anim->list[i];
1253
1254 if (tween->deleted)
1255 continue;
1256
1257 tween->running_since += p_anim->delta_time;
1258
1259 *tween->subject = tween->easing(
1260 tween->running_since,
1261 tween->initial_value,
1262 tween->target_value - tween->initial_value,
1263 tween->duration);
1264
1265 if (tween->running_since >= tween->duration)
1266 {
1267 *tween->subject = tween->target_value;
1268
1269 if (tween->cb)
1270 tween->cb(tween->userdata);
1271
1272 RBUF_REMOVE(p_anim->list, i);
1273 i--;
1274 }
1275 }
1276
1277 if (p_anim->pending_deletes)
1278 {
1279 for (i = 0; i < RBUF_LEN(p_anim->list); i++)
1280 {
1281 struct tween *tween = &p_anim->list[i];
1282 if (tween->deleted)
1283 {
1284 RBUF_REMOVE(p_anim->list, i);
1285 i--;
1286 }
1287 }
1288 p_anim->pending_deletes = false;
1289 }
1290
1291 if (RBUF_LEN(p_anim->pending) > 0)
1292 {
1293 size_t list_len = RBUF_LEN(p_anim->list);
1294 size_t pending_len = RBUF_LEN(p_anim->pending);
1295 RBUF_RESIZE(p_anim->list, list_len + pending_len);
1296 memcpy(p_anim->list + list_len, p_anim->pending,
1297 sizeof(*p_anim->pending) * pending_len);
1298 RBUF_CLEAR(p_anim->pending);
1299 }
1300
1301 p_anim->in_update = false;
1302 p_anim->animation_is_active = RBUF_LEN(p_anim->list) > 0;
1303
1304 return p_anim->animation_is_active;
1305 }
1306
build_ticker_loop_string(const char * src_str,const char * spacer,size_t char_offset1,size_t num_chars1,size_t char_offset2,size_t num_chars2,size_t char_offset3,size_t num_chars3,char * dest_str,size_t dest_str_len)1307 static void build_ticker_loop_string(
1308 const char* src_str, const char *spacer,
1309 size_t char_offset1, size_t num_chars1,
1310 size_t char_offset2, size_t num_chars2,
1311 size_t char_offset3, size_t num_chars3,
1312 char *dest_str, size_t dest_str_len)
1313 {
1314 char tmp[PATH_MAX_LENGTH];
1315
1316 tmp[0] = '\0';
1317 dest_str[0] = '\0';
1318
1319 /* Copy 'trailing' chunk of source string, if required */
1320 if (num_chars1 > 0)
1321 utf8cpy(
1322 dest_str, dest_str_len,
1323 utf8skip(src_str, char_offset1), num_chars1);
1324
1325 /* Copy chunk of spacer string, if required */
1326 if (num_chars2 > 0)
1327 {
1328 utf8cpy(
1329 tmp, sizeof(tmp),
1330 utf8skip(spacer, char_offset2), num_chars2);
1331
1332 strlcat(dest_str, tmp, dest_str_len);
1333 }
1334
1335 /* Copy 'leading' chunk of source string, if required */
1336 if (num_chars3 > 0)
1337 {
1338 utf8cpy(
1339 tmp, sizeof(tmp),
1340 utf8skip(src_str, char_offset3), num_chars3);
1341
1342 strlcat(dest_str, tmp, dest_str_len);
1343 }
1344 }
1345
build_line_ticker_string(size_t num_display_lines,size_t line_offset,struct string_list * lines,char * dest_str,size_t dest_str_len)1346 static void build_line_ticker_string(
1347 size_t num_display_lines, size_t line_offset,
1348 struct string_list *lines,
1349 char *dest_str, size_t dest_str_len)
1350 {
1351 size_t i;
1352
1353 for (i = 0; i < num_display_lines; i++)
1354 {
1355 size_t offset = i + line_offset;
1356 size_t line_index = offset % (lines->size + 1);
1357 bool line_valid = true;
1358
1359 if (line_index >= lines->size)
1360 line_valid = false;
1361
1362 if (line_valid)
1363 strlcat(dest_str, lines->elems[line_index].data, dest_str_len);
1364
1365 if (i < num_display_lines - 1)
1366 strlcat(dest_str, "\n", dest_str_len);
1367 }
1368 }
1369
gfx_animation_ticker(gfx_animation_ctx_ticker_t * ticker)1370 bool gfx_animation_ticker(gfx_animation_ctx_ticker_t *ticker)
1371 {
1372 gfx_animation_t *p_anim = anim_get_ptr();
1373 size_t str_len = utf8len(ticker->str);
1374
1375 if (!ticker->spacer)
1376 ticker->spacer = TICKER_SPACER_DEFAULT;
1377
1378 if ((size_t)str_len <= ticker->len)
1379 {
1380 utf8cpy(ticker->s,
1381 PATH_MAX_LENGTH,
1382 ticker->str,
1383 ticker->len);
1384 return false;
1385 }
1386
1387 if (!ticker->selected)
1388 {
1389 utf8cpy(ticker->s,
1390 PATH_MAX_LENGTH, ticker->str, ticker->len - 3);
1391 strlcat(ticker->s, "...", ticker->len);
1392 return false;
1393 }
1394
1395 /* Note: If we reach this point then str_len > ticker->len
1396 * (previously had an unecessary 'if (str_len > ticker->len)'
1397 * check here...) */
1398 switch (ticker->type_enum)
1399 {
1400 case TICKER_TYPE_LOOP:
1401 {
1402 size_t offset1, offset2, offset3;
1403 size_t width1, width2, width3;
1404
1405 gfx_animation_ticker_loop(
1406 ticker->idx,
1407 ticker->len,
1408 str_len, utf8len(ticker->spacer),
1409 &offset1, &width1,
1410 &offset2, &width2,
1411 &offset3, &width3);
1412
1413 build_ticker_loop_string(
1414 ticker->str, ticker->spacer,
1415 offset1, width1,
1416 offset2, width2,
1417 offset3, width3,
1418 ticker->s, PATH_MAX_LENGTH);
1419 }
1420 break;
1421 case TICKER_TYPE_BOUNCE:
1422 default:
1423 {
1424 size_t offset = gfx_animation_ticker_generic(
1425 ticker->idx,
1426 str_len - ticker->len);
1427
1428 str_len = ticker->len;
1429
1430 utf8cpy(
1431 ticker->s,
1432 PATH_MAX_LENGTH,
1433 utf8skip(ticker->str, offset),
1434 str_len);
1435 }
1436 break;
1437 }
1438
1439 p_anim->ticker_is_active = true;
1440
1441 return true;
1442 }
1443
1444 /* 'Fixed width' font version of gfx_animation_ticker_smooth() */
gfx_animation_ticker_smooth_fw(gfx_animation_t * p_anim,gfx_animation_ctx_ticker_smooth_t * ticker)1445 static bool gfx_animation_ticker_smooth_fw(
1446 gfx_animation_t *p_anim,
1447 gfx_animation_ctx_ticker_smooth_t *ticker)
1448 {
1449 size_t spacer_len = 0;
1450 unsigned glyph_width = ticker->glyph_width;
1451 unsigned src_str_width = 0;
1452 unsigned spacer_width = 0;
1453 bool success = false;
1454 bool is_active = false;
1455
1456 /* Sanity check has already been performed by
1457 * gfx_animation_ticker_smooth() - no need to
1458 * repeat */
1459
1460 /* Get length + width of src string */
1461 size_t src_str_len = utf8len(ticker->src_str);
1462 if (src_str_len < 1)
1463 goto end;
1464
1465 src_str_width = src_str_len * glyph_width;
1466
1467 /* If src string width is <= text field width, we
1468 * can just copy the entire string */
1469 if (src_str_width <= ticker->field_width)
1470 {
1471 utf8cpy(ticker->dst_str, ticker->dst_str_len,
1472 ticker->src_str, src_str_len);
1473 if (ticker->dst_str_width)
1474 *ticker->dst_str_width = src_str_width;
1475 *ticker->x_offset = 0;
1476 success = true;
1477 goto end;
1478 }
1479
1480 /* If entry is not selected, just clip input string
1481 * and add '...' suffix */
1482 if (!ticker->selected)
1483 {
1484 unsigned num_chars = 0;
1485 unsigned suffix_len = 3;
1486 unsigned suffix_width = suffix_len * glyph_width;
1487
1488 /* Sanity check */
1489 if (ticker->field_width < suffix_width)
1490 goto end;
1491
1492 /* Determine number of characters to copy */
1493 num_chars = (ticker->field_width - suffix_width) / glyph_width;
1494
1495 /* Copy string segment + add suffix */
1496 utf8cpy(ticker->dst_str, ticker->dst_str_len, ticker->src_str, num_chars);
1497 strlcat(ticker->dst_str, "...", ticker->dst_str_len);
1498
1499 if (ticker->dst_str_width)
1500 *ticker->dst_str_width = (num_chars * glyph_width) + suffix_width;
1501 *ticker->x_offset = 0;
1502 success = true;
1503 goto end;
1504 }
1505
1506 /* If we get this far, then a scrolling animation
1507 * is required... */
1508
1509 /* Use default spacer, if none is provided */
1510 if (!ticker->spacer)
1511 ticker->spacer = TICKER_SPACER_DEFAULT;
1512
1513 /* Get length + width of spacer */
1514 spacer_len = utf8len(ticker->spacer);
1515 if (spacer_len < 1)
1516 goto end;
1517
1518 spacer_width = spacer_len * glyph_width;
1519
1520 /* Determine animation type */
1521 switch (ticker->type_enum)
1522 {
1523 case TICKER_TYPE_LOOP:
1524 {
1525 unsigned char_offset1 = 0;
1526 unsigned num_chars1 = 0;
1527 unsigned char_offset2 = 0;
1528 unsigned num_chars2 = 0;
1529 unsigned char_offset3 = 0;
1530 unsigned num_chars3 = 0;
1531
1532 gfx_animation_ticker_smooth_loop_fw(
1533 ticker->idx,
1534 src_str_width, src_str_len, spacer_width, spacer_len,
1535 glyph_width, ticker->field_width,
1536 &char_offset1, &num_chars1,
1537 &char_offset2, &num_chars2,
1538 &char_offset3, &num_chars3,
1539 ticker->x_offset);
1540
1541 build_ticker_loop_string(
1542 ticker->src_str, ticker->spacer,
1543 char_offset1, num_chars1,
1544 char_offset2, num_chars2,
1545 char_offset3, num_chars3,
1546 ticker->dst_str, ticker->dst_str_len);
1547
1548 if (ticker->dst_str_width)
1549 *ticker->dst_str_width = (num_chars1 + num_chars2 + num_chars3) * glyph_width;
1550
1551 break;
1552 }
1553 case TICKER_TYPE_BOUNCE:
1554 default:
1555 {
1556 unsigned char_offset = 0;
1557 unsigned num_chars = 0;
1558
1559 ticker->dst_str[0] = '\0';
1560
1561 gfx_animation_ticker_smooth_generic_fw(
1562 ticker->idx,
1563 src_str_width, src_str_len, glyph_width, ticker->field_width,
1564 &char_offset, &num_chars, ticker->x_offset);
1565
1566 /* Copy required substring */
1567 if (num_chars > 0)
1568 utf8cpy(
1569 ticker->dst_str, ticker->dst_str_len,
1570 utf8skip(ticker->src_str, char_offset), num_chars);
1571
1572 if (ticker->dst_str_width)
1573 *ticker->dst_str_width = num_chars * glyph_width;
1574
1575 break;
1576 }
1577 }
1578
1579 success = true;
1580 is_active = true;
1581 p_anim->ticker_is_active = true;
1582
1583 end:
1584
1585 if (!success)
1586 {
1587 *ticker->x_offset = 0;
1588
1589 if (ticker->dst_str_len > 0)
1590 ticker->dst_str[0] = '\0';
1591 }
1592
1593 return is_active;
1594 }
1595
gfx_animation_ticker_smooth(gfx_animation_ctx_ticker_smooth_t * ticker)1596 bool gfx_animation_ticker_smooth(gfx_animation_ctx_ticker_smooth_t *ticker)
1597 {
1598 size_t i;
1599 size_t src_str_len = 0;
1600 size_t spacer_len = 0;
1601 unsigned small_src_char_widths[64] = {0};
1602 unsigned src_str_width = 0;
1603 unsigned spacer_width = 0;
1604 unsigned *src_char_widths = NULL;
1605 unsigned *spacer_char_widths = NULL;
1606 const char *str_ptr = NULL;
1607 bool success = false;
1608 bool is_active = false;
1609 gfx_animation_t *p_anim = anim_get_ptr();
1610
1611 /* Sanity check */
1612 if (string_is_empty(ticker->src_str) ||
1613 (ticker->dst_str_len < 1) ||
1614 (ticker->field_width < 1) ||
1615 (!ticker->font && (ticker->glyph_width < 1)))
1616 goto end;
1617
1618 /* If we are using a fixed width font (ticker->font == NULL),
1619 * switch to optimised code path */
1620 if (!ticker->font)
1621 return gfx_animation_ticker_smooth_fw(p_anim, ticker);
1622
1623 /* Find the display width of each character in
1624 * the src string + total width */
1625 src_str_len = utf8len(ticker->src_str);
1626 if (src_str_len < 1)
1627 goto end;
1628
1629 src_char_widths = small_src_char_widths;
1630
1631 if (src_str_len > ARRAY_SIZE(small_src_char_widths))
1632 {
1633 src_char_widths = (unsigned*)calloc(src_str_len, sizeof(unsigned));
1634 if (!src_char_widths)
1635 goto end;
1636 }
1637
1638 str_ptr = ticker->src_str;
1639 for (i = 0; i < src_str_len; i++)
1640 {
1641 int glyph_width = font_driver_get_message_width(
1642 ticker->font, str_ptr, 1, ticker->font_scale);
1643
1644 if (glyph_width < 0)
1645 goto end;
1646
1647 src_char_widths[i] = (unsigned)glyph_width;
1648 src_str_width += (unsigned)glyph_width;
1649
1650 str_ptr = utf8skip(str_ptr, 1);
1651 }
1652
1653 /* If total src string width is <= text field width, we
1654 * can just copy the entire string */
1655 if (src_str_width <= ticker->field_width)
1656 {
1657 utf8cpy(ticker->dst_str, ticker->dst_str_len,
1658 ticker->src_str, src_str_len);
1659
1660 if (ticker->dst_str_width)
1661 *ticker->dst_str_width = src_str_width;
1662 *ticker->x_offset = 0;
1663 success = true;
1664 goto end;
1665 }
1666
1667 /* If entry is not selected, just clip input string
1668 * and add '...' suffix */
1669 if (!ticker->selected)
1670 {
1671 unsigned text_width;
1672 unsigned current_width = 0;
1673 unsigned num_chars = 0;
1674 int period_width =
1675 font_driver_get_message_width(ticker->font,
1676 ".", 1, ticker->font_scale);
1677
1678 /* Sanity check */
1679 if (period_width < 0)
1680 goto end;
1681
1682 if (ticker->field_width < (3 * (unsigned)period_width))
1683 goto end;
1684
1685 /* Determine number of characters to copy */
1686 text_width = ticker->field_width - (3 * period_width);
1687
1688 for (;;)
1689 {
1690 current_width += src_char_widths[num_chars];
1691
1692 if (current_width > text_width)
1693 {
1694 /* Have to go back one in order to get 'actual'
1695 * value for dst_str_width */
1696 current_width -= src_char_widths[num_chars];
1697 break;
1698 }
1699
1700 num_chars++;
1701 }
1702
1703 /* Copy string segment + add suffix */
1704 utf8cpy(ticker->dst_str, ticker->dst_str_len,
1705 ticker->src_str, num_chars);
1706 strlcat(ticker->dst_str, "...", ticker->dst_str_len);
1707
1708 if (ticker->dst_str_width)
1709 *ticker->dst_str_width = current_width + (3 * period_width);
1710 *ticker->x_offset = 0;
1711 success = true;
1712 goto end;
1713 }
1714
1715 /* If we get this far, then a scrolling animation
1716 * is required... */
1717
1718 /* Use default spacer, if none is provided */
1719 if (!ticker->spacer)
1720 ticker->spacer = TICKER_SPACER_DEFAULT;
1721
1722 /* Find the display width of each character in
1723 * the spacer */
1724 spacer_len = utf8len(ticker->spacer);
1725 if (spacer_len < 1)
1726 goto end;
1727
1728 spacer_char_widths = (unsigned*)calloc(spacer_len, sizeof(unsigned));
1729 if (!spacer_char_widths)
1730 goto end;
1731
1732 str_ptr = ticker->spacer;
1733 for (i = 0; i < spacer_len; i++)
1734 {
1735 int glyph_width = font_driver_get_message_width(
1736 ticker->font, str_ptr, 1, ticker->font_scale);
1737
1738 if (glyph_width < 0)
1739 goto end;
1740
1741 spacer_char_widths[i] = (unsigned)glyph_width;
1742 spacer_width += (unsigned)glyph_width;
1743
1744 str_ptr = utf8skip(str_ptr, 1);
1745 }
1746
1747 /* Determine animation type */
1748 switch (ticker->type_enum)
1749 {
1750 case TICKER_TYPE_LOOP:
1751 {
1752 unsigned char_offset1 = 0;
1753 unsigned num_chars1 = 0;
1754 unsigned char_offset2 = 0;
1755 unsigned num_chars2 = 0;
1756 unsigned char_offset3 = 0;
1757 unsigned num_chars3 = 0;
1758
1759 gfx_animation_ticker_smooth_loop(
1760 ticker->idx,
1761 src_char_widths, src_str_len,
1762 spacer_char_widths, spacer_len,
1763 src_str_width, spacer_width, ticker->field_width,
1764 &char_offset1, &num_chars1,
1765 &char_offset2, &num_chars2,
1766 &char_offset3, &num_chars3,
1767 ticker->x_offset, ticker->dst_str_width);
1768
1769 build_ticker_loop_string(
1770 ticker->src_str, ticker->spacer,
1771 char_offset1, num_chars1,
1772 char_offset2, num_chars2,
1773 char_offset3, num_chars3,
1774 ticker->dst_str, ticker->dst_str_len);
1775
1776 break;
1777 }
1778 case TICKER_TYPE_BOUNCE:
1779 default:
1780 {
1781 unsigned char_offset = 0;
1782 unsigned num_chars = 0;
1783
1784 ticker->dst_str[0] = '\0';
1785
1786 gfx_animation_ticker_smooth_generic(
1787 ticker->idx,
1788 src_char_widths, src_str_len,
1789 src_str_width, ticker->field_width,
1790 &char_offset, &num_chars,
1791 ticker->x_offset, ticker->dst_str_width);
1792
1793 /* Copy required substring */
1794 if (num_chars > 0)
1795 utf8cpy(
1796 ticker->dst_str, ticker->dst_str_len,
1797 utf8skip(ticker->src_str, char_offset), num_chars);
1798
1799 break;
1800 }
1801 }
1802
1803 success = true;
1804 is_active = true;
1805 p_anim->ticker_is_active = true;
1806
1807 end:
1808
1809 if (src_char_widths != small_src_char_widths && src_char_widths)
1810 {
1811 free(src_char_widths);
1812 src_char_widths = NULL;
1813 }
1814
1815 if (spacer_char_widths)
1816 {
1817 free(spacer_char_widths);
1818 spacer_char_widths = NULL;
1819 }
1820
1821 if (!success)
1822 {
1823 *ticker->x_offset = 0;
1824
1825 if (ticker->dst_str_len > 0)
1826 ticker->dst_str[0] = '\0';
1827 }
1828
1829 return is_active;
1830 }
1831
gfx_animation_line_ticker(gfx_animation_ctx_line_ticker_t * line_ticker)1832 bool gfx_animation_line_ticker(gfx_animation_ctx_line_ticker_t *line_ticker)
1833 {
1834 char *wrapped_str = NULL;
1835 size_t wrapped_str_len = 0;
1836 struct string_list lines = {0};
1837 size_t line_offset = 0;
1838 bool success = false;
1839 bool is_active = false;
1840 gfx_animation_t *p_anim = anim_get_ptr();
1841
1842 /* Sanity check */
1843 if (!line_ticker)
1844 return false;
1845
1846 if (string_is_empty(line_ticker->str) ||
1847 (line_ticker->line_len < 1) ||
1848 (line_ticker->max_lines < 1))
1849 goto end;
1850
1851 /* Line wrap input string */
1852 wrapped_str_len = strlen(line_ticker->str) + 1 + 10; /* 10 bytes use for inserting '\n' */
1853 wrapped_str = (char*)malloc(wrapped_str_len);
1854 if (!wrapped_str)
1855 goto end;
1856 wrapped_str[0] = '\0';
1857
1858 word_wrap(
1859 wrapped_str,
1860 wrapped_str_len,
1861 line_ticker->str,
1862 (int)line_ticker->line_len,
1863 100, 0);
1864
1865 if (string_is_empty(wrapped_str))
1866 goto end;
1867
1868 /* Split into component lines */
1869 string_list_initialize(&lines);
1870 if (!string_split_noalloc(&lines, wrapped_str, "\n"))
1871 goto end;
1872
1873 /* Check whether total number of lines fits within
1874 * the set limit */
1875 if (lines.size <= line_ticker->max_lines)
1876 {
1877 strlcpy(line_ticker->s, wrapped_str, line_ticker->len);
1878 success = true;
1879 goto end;
1880 }
1881
1882 /* Determine offset of first line in wrapped string */
1883 switch (line_ticker->type_enum)
1884 {
1885 case TICKER_TYPE_LOOP:
1886 line_offset = gfx_animation_line_ticker_loop(
1887 line_ticker->idx,
1888 line_ticker->line_len,
1889 lines.size);
1890 break;
1891 case TICKER_TYPE_BOUNCE:
1892 default:
1893 line_offset = gfx_animation_line_ticker_generic(
1894 line_ticker->idx,
1895 line_ticker->line_len,
1896 line_ticker->max_lines,
1897 lines.size);
1898
1899 break;
1900 }
1901
1902 /* Build output string from required lines */
1903 build_line_ticker_string(
1904 line_ticker->max_lines, line_offset, &lines,
1905 line_ticker->s, line_ticker->len);
1906
1907 success = true;
1908 is_active = true;
1909 p_anim->ticker_is_active = true;
1910
1911 end:
1912
1913 if (wrapped_str)
1914 {
1915 free(wrapped_str);
1916 wrapped_str = NULL;
1917 }
1918
1919 string_list_deinitialize(&lines);
1920 if (!success)
1921 if (line_ticker->len > 0)
1922 line_ticker->s[0] = '\0';
1923
1924 return is_active;
1925 }
1926
gfx_animation_line_ticker_smooth(gfx_animation_ctx_line_ticker_smooth_t * line_ticker)1927 bool gfx_animation_line_ticker_smooth(gfx_animation_ctx_line_ticker_smooth_t *line_ticker)
1928 {
1929 char *wrapped_str = NULL;
1930 size_t wrapped_str_len = 0;
1931 struct string_list lines = {0};
1932 int glyph_width = 0;
1933 int glyph_height = 0;
1934 size_t line_len = 0;
1935 size_t max_display_lines = 0;
1936 size_t num_display_lines = 0;
1937 size_t line_offset = 0;
1938 size_t top_fade_line_offset = 0;
1939 size_t bottom_fade_line_offset = 0;
1940 bool fade_active = false;
1941 bool success = false;
1942 bool is_active = false;
1943 gfx_animation_t *p_anim = anim_get_ptr();
1944 const char *wideglyph_str = msg_hash_get_wideglyph_str();
1945 int wideglyph_width = 100;
1946 void (*word_wrap_func)(char *dst, size_t dst_size, const char *src,
1947 int line_width, int wideglyph_width, unsigned max_lines)
1948 = wideglyph_str ? word_wrap_wideglyph : word_wrap;
1949
1950 /* Sanity check */
1951 if (!line_ticker)
1952 return false;
1953
1954 if (!line_ticker->font ||
1955 string_is_empty(line_ticker->src_str) ||
1956 (line_ticker->field_width < 1) ||
1957 (line_ticker->field_height < 1))
1958 goto end;
1959
1960 /* Get font dimensions */
1961
1962 /* > Width
1963 * This is a bit of a fudge. Performing a 'font aware'
1964 * (i.e. character display width) word wrap is too CPU
1965 * intensive, so we just sample the width of a common
1966 * character and hope for the best. (We choose 'a' because
1967 * this is what Ozone uses for spacing calculations, and
1968 * it is proven to work quite well) */
1969 glyph_width = font_driver_get_message_width(
1970 line_ticker->font, "a", 1, line_ticker->font_scale);
1971
1972 if (glyph_width <= 0)
1973 goto end;
1974
1975 if (wideglyph_str)
1976 {
1977 wideglyph_width = font_driver_get_message_width(
1978 line_ticker->font, wideglyph_str, strlen(wideglyph_str),
1979 line_ticker->font_scale);
1980
1981 if (wideglyph_width > 0)
1982 wideglyph_width = wideglyph_width * 100 / glyph_width;
1983 else
1984 wideglyph_width = 100;
1985 }
1986
1987 /* > Height */
1988 glyph_height = font_driver_get_line_height(
1989 line_ticker->font, line_ticker->font_scale);
1990
1991 if (glyph_height <= 0)
1992 goto end;
1993
1994 /* Determine line wrap parameters */
1995 line_len = (size_t)(line_ticker->field_width / glyph_width);
1996 max_display_lines = (size_t)(line_ticker->field_height / glyph_height);
1997
1998 if ((line_len < 1) || (max_display_lines < 1))
1999 goto end;
2000
2001 /* Line wrap input string */
2002 wrapped_str_len = strlen(line_ticker->src_str) + 1 + 10; /* 10 bytes use for inserting '\n' */
2003 wrapped_str = (char*)malloc(wrapped_str_len);
2004 if (!wrapped_str)
2005 goto end;
2006 wrapped_str[0] = '\0';
2007
2008 (word_wrap_func)(
2009 wrapped_str,
2010 wrapped_str_len,
2011 line_ticker->src_str,
2012 (int)line_len,
2013 wideglyph_width, 0);
2014
2015 if (string_is_empty(wrapped_str))
2016 goto end;
2017
2018 string_list_initialize(&lines);
2019 /* Split into component lines */
2020 if (!string_split_noalloc(&lines, wrapped_str, "\n"))
2021 goto end;
2022
2023 /* Check whether total number of lines fits within
2024 * the set field limit */
2025 if (lines.size <= max_display_lines)
2026 {
2027 strlcpy(line_ticker->dst_str, wrapped_str, line_ticker->dst_str_len);
2028 *line_ticker->y_offset = 0.0f;
2029
2030 /* No fade animation is required */
2031 if (line_ticker->fade_enabled)
2032 {
2033 if (line_ticker->top_fade_str_len > 0)
2034 line_ticker->top_fade_str[0] = '\0';
2035
2036 if (line_ticker->bottom_fade_str_len > 0)
2037 line_ticker->bottom_fade_str[0] = '\0';
2038
2039 *line_ticker->top_fade_y_offset = 0.0f;
2040 *line_ticker->bottom_fade_y_offset = 0.0f;
2041
2042 *line_ticker->top_fade_alpha = 0.0f;
2043 *line_ticker->bottom_fade_alpha = 0.0f;
2044 }
2045
2046 success = true;
2047 goto end;
2048 }
2049
2050 /* Determine which lines should be shown, along with
2051 * y axis draw offset */
2052 switch (line_ticker->type_enum)
2053 {
2054 case TICKER_TYPE_LOOP:
2055 gfx_animation_line_ticker_smooth_loop(
2056 line_ticker->idx,
2057 line_ticker->fade_enabled,
2058 line_len, (size_t)glyph_height,
2059 max_display_lines, lines.size,
2060 &num_display_lines, &line_offset, line_ticker->y_offset,
2061 &fade_active,
2062 &top_fade_line_offset, line_ticker->top_fade_y_offset, line_ticker->top_fade_alpha,
2063 &bottom_fade_line_offset, line_ticker->bottom_fade_y_offset, line_ticker->bottom_fade_alpha);
2064
2065 break;
2066 case TICKER_TYPE_BOUNCE:
2067 default:
2068 gfx_animation_line_ticker_smooth_generic(
2069 line_ticker->idx,
2070 line_ticker->fade_enabled,
2071 line_len, (size_t)glyph_height,
2072 max_display_lines, lines.size,
2073 &num_display_lines, &line_offset, line_ticker->y_offset,
2074 &fade_active,
2075 &top_fade_line_offset, line_ticker->top_fade_y_offset, line_ticker->top_fade_alpha,
2076 &bottom_fade_line_offset, line_ticker->bottom_fade_y_offset, line_ticker->bottom_fade_alpha);
2077
2078 break;
2079 }
2080
2081 /* Build output string from required lines */
2082 build_line_ticker_string(
2083 num_display_lines, line_offset, &lines,
2084 line_ticker->dst_str, line_ticker->dst_str_len);
2085
2086 /* Extract top/bottom fade strings, if required */
2087 if (fade_active)
2088 {
2089 /* We waste a handful of clock cycles by using
2090 * build_line_ticker_string() here, but it saves
2091 * rewriting a heap of code... */
2092 build_line_ticker_string(
2093 1, top_fade_line_offset, &lines,
2094 line_ticker->top_fade_str, line_ticker->top_fade_str_len);
2095
2096 build_line_ticker_string(
2097 1, bottom_fade_line_offset, &lines,
2098 line_ticker->bottom_fade_str, line_ticker->bottom_fade_str_len);
2099 }
2100
2101 success = true;
2102 is_active = true;
2103 p_anim->ticker_is_active = true;
2104
2105 end:
2106
2107 if (wrapped_str)
2108 {
2109 free(wrapped_str);
2110 wrapped_str = NULL;
2111 }
2112
2113 string_list_deinitialize(&lines);
2114
2115 if (!success)
2116 {
2117 if (line_ticker->dst_str_len > 0)
2118 line_ticker->dst_str[0] = '\0';
2119
2120 if (line_ticker->fade_enabled)
2121 {
2122 if (line_ticker->top_fade_str_len > 0)
2123 line_ticker->top_fade_str[0] = '\0';
2124
2125 if (line_ticker->bottom_fade_str_len > 0)
2126 line_ticker->bottom_fade_str[0] = '\0';
2127
2128 *line_ticker->top_fade_alpha = 0.0f;
2129 *line_ticker->bottom_fade_alpha = 0.0f;
2130 }
2131 }
2132
2133 return is_active;
2134 }
2135
gfx_animation_kill_by_tag(uintptr_t * tag)2136 bool gfx_animation_kill_by_tag(uintptr_t *tag)
2137 {
2138 unsigned i;
2139 gfx_animation_t *p_anim = anim_get_ptr();
2140
2141 if (!tag || *tag == (uintptr_t)-1)
2142 return false;
2143
2144 /* Scan animation list */
2145 for (i = 0; i < RBUF_LEN(p_anim->list); ++i)
2146 {
2147 struct tween *t = &p_anim->list[i];
2148
2149 if (t->tag != *tag)
2150 continue;
2151
2152 /* If we are currently inside gfx_animation_update(),
2153 * we are already looping over p_anim->list entries
2154 * > Cannot modify p_anim->list now, so schedule a
2155 * delete for when the gfx_animation_update() loop
2156 * is complete */
2157 if (p_anim->in_update)
2158 {
2159 t->deleted = true;
2160 p_anim->pending_deletes = true;
2161 }
2162 else
2163 {
2164 RBUF_REMOVE(p_anim->list, i);
2165 --i;
2166 }
2167 }
2168
2169 /* If we are currently inside gfx_animation_update(),
2170 * also have to scan *pending* animation list
2171 * (otherwise any entries that are simultaneously added
2172 * and deleted inside gfx_animation_update() won't get
2173 * deleted at all, producing utter chaos) */
2174 if (p_anim->in_update)
2175 {
2176 for (i = 0; i < RBUF_LEN(p_anim->pending); ++i)
2177 {
2178 struct tween *t = &p_anim->pending[i];
2179
2180 if (t->tag != *tag)
2181 continue;
2182
2183 RBUF_REMOVE(p_anim->pending, i);
2184 --i;
2185 }
2186 }
2187
2188 return true;
2189 }
2190
gfx_animation_deinit(gfx_animation_t * p_anim)2191 void gfx_animation_deinit(gfx_animation_t *p_anim)
2192 {
2193 if (!p_anim)
2194 return;
2195 RBUF_FREE(p_anim->list);
2196 RBUF_FREE(p_anim->pending);
2197 if (p_anim->updatetime_cb)
2198 p_anim->updatetime_cb = NULL;
2199 memset(p_anim, 0, sizeof(*p_anim));
2200 }
2201
gfx_animation_timer_start(gfx_timer_t * timer,gfx_timer_ctx_entry_t * timer_entry)2202 void gfx_animation_timer_start(gfx_timer_t *timer, gfx_timer_ctx_entry_t *timer_entry)
2203 {
2204 gfx_animation_ctx_entry_t entry;
2205 uintptr_t tag = (uintptr_t) timer;
2206
2207 gfx_animation_kill_by_tag(&tag);
2208
2209 *timer = 0.0f;
2210
2211 entry.easing_enum = EASING_LINEAR;
2212 entry.tag = tag;
2213 entry.duration = timer_entry->duration;
2214 entry.target_value = 1.0f;
2215 entry.subject = timer;
2216 entry.cb = timer_entry->cb;
2217 entry.userdata = timer_entry->userdata;
2218
2219 gfx_animation_push(&entry);
2220 }
2221