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