1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3  *  Copyright (C) 2011-2017 - Daniel De Matteis
4  *  Copyright (C) 2012-2015 - Michael Lelli
5  *  Copyright (C) 2016-2019 - Brad Parker
6  *
7  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
8  *  of the GNU General Public License as published by the Free Software Found-
9  *  ation, either version 3 of the License, or (at your option) any later version.
10  *
11  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13  *  PURPOSE.  See the GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License along with RetroArch.
16  *  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <stdlib.h>
20 #include <stddef.h>
21 #include <stdint.h>
22 #include <string.h>
23 #include <limits.h>
24 
25 #include <string/stdstring.h>
26 #include <lists/file_list.h>
27 #include <lists/string_list.h>
28 #include <compat/strl.h>
29 #include <compat/posix_string.h>
30 #include <encodings/utf.h>
31 #include <file/config_file.h>
32 #include <file/file_path.h>
33 #include <formats/image.h>
34 
35 #include <retro_inline.h>
36 #include <gfx/scaler/scaler.h>
37 
38 #ifdef HAVE_CONFIG_H
39 #include "../../config.h"
40 #endif
41 
42 #ifdef HAVE_GFX_WIDGETS
43 #include "../../gfx/gfx_widgets.h"
44 #endif
45 
46 #include "../../frontend/frontend_driver.h"
47 
48 #include "../menu_driver.h"
49 #include "../../gfx/gfx_animation.h"
50 
51 #include "../../input/input_osk.h"
52 
53 #include "../../configuration.h"
54 #include "../../gfx/drivers_font_renderer/bitmap.h"
55 #include "../../gfx/drivers_font_renderer/bitmapfont_10x10.h"
56 
57 /* Thumbnail additions */
58 #include "../../gfx/gfx_thumbnail_path.h"
59 #include "../../tasks/tasks_internal.h"
60 
61 #if defined(GEKKO)
62 /* Required for the Wii build, since we have
63  * to query the hardware for the actual display
64  * aspect ratio... */
65 #include "../../wii/libogc/include/ogc/conf.h"
66 #endif
67 
68 #if defined(GEKKO)
69 /* When running on the Wii, need to round down the
70  * frame buffer width value such that the last two
71  * bits are zero */
72 #define RGUI_ROUND_FB_WIDTH(width) ((unsigned)(width) & ~3)
73 #else
74 /* On all other platforms, just want to round width
75  * down to the nearest multiple of 2 */
76 #define RGUI_ROUND_FB_WIDTH(width) ((unsigned)(width) & ~1)
77 #endif
78 
79 #define RGUI_MIN_FB_HEIGHT 192
80 #define RGUI_MIN_FB_WIDTH 256
81 #define RGUI_MAX_FB_WIDTH 426
82 
83 #if defined(DINGUX)
84 #if defined(RS90)
85 /* The RS-90 uses a fixed framebuffer size
86  * of 240x160 */
87 #define RGUI_DINGUX_ASPECT_RATIO RGUI_ASPECT_RATIO_3_2
88 #define RGUI_DINGUX_FB_WIDTH     240
89 #define RGUI_DINGUX_FB_HEIGHT    160
90 #else
91 /* Other Dingux devices (RG350 etc.) use a
92  * fixed framebuffer size of 320x240 */
93 #define RGUI_DINGUX_ASPECT_RATIO RGUI_ASPECT_RATIO_4_3
94 #define RGUI_DINGUX_FB_WIDTH     320
95 #define RGUI_DINGUX_FB_HEIGHT    240
96 #endif
97 #endif
98 
99 /* Maximum entry value length in characters
100  * when using fixed with layouts
101  * (i.e. Maximum possible 'spacing' as
102  * defined in menu_cbs_get_value.c) */
103 #define RGUI_ENTRY_VALUE_MAXLEN 19
104 
105 /* Maximum fraction of the terminal width that
106  * may be used for displaying entry values */
107 #define RGUI_ENTRY_VALUE_MAXLEN_FRACTION (3.0f / 8.0f)
108 
109 #define RGUI_TICKER_SPACER " | "
110 
111 #define RGUI_NUM_FONT_GLYPHS_REGULAR 128
112 #define RGUI_NUM_FONT_GLYPHS_EXTENDED 256
113 
114 #define RGUI_NUM_PARTICLES 256
115 
116 #ifndef PI
117 #define PI 3.14159265359f
118 #endif
119 
120 #define RGUI_BATTERY_WARN_THRESHOLD 20
121 
122 typedef struct
123 {
124    uint32_t hover_color;
125    uint32_t normal_color;
126    uint32_t title_color;
127    uint32_t bg_dark_color;
128    uint32_t bg_light_color;
129    uint32_t border_dark_color;
130    uint32_t border_light_color;
131    uint32_t shadow_color;
132    uint32_t particle_color;
133 } rgui_theme_t;
134 
135 static const rgui_theme_t rgui_theme_classic_red = {
136    0xFFFF362B, /* hover_color */
137    0xFFFFFFFF, /* normal_color */
138    0xFFFF362B, /* title_color */
139    0xC0202020, /* bg_dark_color */
140    0xC0404040, /* bg_light_color */
141    0xC08C0000, /* border_dark_color */
142    0xC0CC0E03, /* border_light_color */
143    0xC0000000, /* shadow_color */
144    0xC09E8686  /* particle_color */
145 };
146 
147 static const rgui_theme_t rgui_theme_opaque_classic_red = {
148    0xFFFF362B, /* hover_color */
149    0xFFFFFFFF, /* normal_color */
150    0xFFFF362B, /* title_color */
151    0xFF1B1B1B, /* bg_dark_color */
152    0xFF363636, /* bg_light_color */
153    0xFF6D0000, /* border_dark_color */
154    0xFFA30000, /* border_light_color */
155    0xFF000000, /* shadow_color */
156    0xFF7A6D6D  /* particle_color */
157 };
158 
159 static const rgui_theme_t rgui_theme_classic_orange = {
160    0xFFF87217, /* hover_color */
161    0xFFFFFFFF, /* normal_color */
162    0xFFF87217, /* title_color */
163    0xC0202020, /* bg_dark_color */
164    0xC0404040, /* bg_light_color */
165    0xC0962800, /* border_dark_color */
166    0xC0E46C03, /* border_light_color */
167    0xC0000000, /* shadow_color */
168    0xC09E9286  /* particle_color */
169 };
170 
171 static const rgui_theme_t rgui_theme_opaque_classic_orange = {
172    0xFFF87217, /* hover_color */
173    0xFFFFFFFF, /* normal_color */
174    0xFFF87217, /* title_color */
175    0xFF1B1B1B, /* bg_dark_color */
176    0xFF363636, /* bg_light_color */
177    0xFF7A1B00, /* border_dark_color */
178    0xFFBE5200, /* border_light_color */
179    0xFF000000, /* shadow_color */
180    0xFF7A7A6D  /* particle_color */
181 };
182 
183 static const rgui_theme_t rgui_theme_classic_yellow = {
184    0xFFFFD801, /* hover_color */
185    0xFFFFFFFF, /* normal_color */
186    0xFFFFD801, /* title_color */
187    0xC0202020, /* bg_dark_color */
188    0xC0404040, /* bg_light_color */
189    0xC0AC7800, /* border_dark_color */
190    0xC0F3C60D, /* border_light_color */
191    0xC0000000, /* shadow_color */
192    0xC0999581  /* particle_color */
193 };
194 
195 static const rgui_theme_t rgui_theme_opaque_classic_yellow = {
196    0xFFFFD801, /* hover_color */
197    0xFFFFFFFF, /* normal_color */
198    0xFFFFD801, /* title_color */
199    0xFF1B1B1B, /* bg_dark_color */
200    0xFF363636, /* bg_light_color */
201    0xFF885F00, /* border_dark_color */
202    0xFFCCA300, /* border_light_color */
203    0xFF000000, /* shadow_color */
204    0xFF7A7A6D  /* particle_color */
205 };
206 
207 static const rgui_theme_t rgui_theme_classic_green = {
208    0xFF64FF64, /* hover_color */
209    0xFFFFFFFF, /* normal_color */
210    0xFF64FF64, /* title_color */
211    0xC0202020, /* bg_dark_color */
212    0xC0404040, /* bg_light_color */
213    0xC0204020, /* border_dark_color */
214    0xC0408040, /* border_light_color */
215    0xC0000000, /* shadow_color */
216    0xC0879E87  /* particle_color */
217 };
218 
219 static const rgui_theme_t rgui_theme_opaque_classic_green = {
220    0xFF64FF64, /* hover_color */
221    0xFFFFFFFF, /* normal_color */
222    0xFF64FF64, /* title_color */
223    0xFF1B1B1B, /* bg_dark_color */
224    0xFF363636, /* bg_light_color */
225    0xFF1B361B, /* border_dark_color */
226    0xFF366D36, /* border_light_color */
227    0xFF000000, /* shadow_color */
228    0xFF6D7A6D  /* particle_color */
229 };
230 
231 static const rgui_theme_t rgui_theme_classic_blue = {
232    0xFF48BEFF, /* hover_color */
233    0xFFFFFFFF, /* normal_color */
234    0xFF48BEFF, /* title_color */
235    0xC0202020, /* bg_dark_color */
236    0xC0404040, /* bg_light_color */
237    0xC0005BA6, /* border_dark_color */
238    0xC02E94E2, /* border_light_color */
239    0xC0000000, /* shadow_color */
240    0xC086949E  /* particle_color */
241 };
242 
243 static const rgui_theme_t rgui_theme_opaque_classic_blue = {
244    0xFF48BEFF, /* hover_color */
245    0xFFFFFFFF, /* normal_color */
246    0xFF48BEFF, /* title_color */
247    0xFF1B1B1B, /* bg_dark_color */
248    0xFF363636, /* bg_light_color */
249    0xFF004488, /* border_dark_color */
250    0xFF1B7ABE, /* border_light_color */
251    0xFF000000, /* shadow_color */
252    0xFF6D7A7A  /* particle_color */
253 };
254 
255 static const rgui_theme_t rgui_theme_classic_violet = {
256    0xFFD86EFF, /* hover_color */
257    0xFFFFFFFF, /* normal_color */
258    0xFFD86EFF, /* title_color */
259    0xC0202020, /* bg_dark_color */
260    0xC0404040, /* bg_light_color */
261    0xC04C0A60, /* border_dark_color */
262    0xC0842DCE, /* border_light_color */
263    0xC0000000, /* shadow_color */
264    0xC08E8299  /* particle_color */
265 };
266 
267 static const rgui_theme_t rgui_theme_opaque_classic_violet = {
268    0xFFD86EFF, /* hover_color */
269    0xFFFFFFFF, /* normal_color */
270    0xFFD86EFF, /* title_color */
271    0xFF1B1B1B, /* bg_dark_color */
272    0xFF363636, /* bg_light_color */
273    0xFF360052, /* border_dark_color */
274    0xFF6D1BA3, /* border_light_color */
275    0xFF000000, /* shadow_color */
276    0xFF6D6D7A  /* particle_color */
277 };
278 
279 static const rgui_theme_t rgui_theme_classic_grey = {
280    0xFFB6C1C7, /* hover_color */
281    0xFFFFFFFF, /* normal_color */
282    0xFFB6C1C7, /* title_color */
283    0xC0202020, /* bg_dark_color */
284    0xC0404040, /* bg_light_color */
285    0xC0505050, /* border_dark_color */
286    0xC0798A99, /* border_light_color */
287    0xC0000000, /* shadow_color */
288    0xC078828A  /* particle_color */
289 };
290 
291 static const rgui_theme_t rgui_theme_opaque_classic_grey = {
292    0xFFB6C1C7, /* hover_color */
293    0xFFFFFFFF, /* normal_color */
294    0xFFB6C1C7, /* title_color */
295    0xFF1B1B1B, /* bg_dark_color */
296    0xFF363636, /* bg_light_color */
297    0xFF444444, /* border_dark_color */
298    0xFF5F6D7A, /* border_light_color */
299    0xFF000000, /* shadow_color */
300    0xFF5F6D6D  /* particle_color */
301 };
302 
303 static const rgui_theme_t rgui_theme_legacy_red = {
304    0xFFFFBDBD, /* hover_color */
305    0xFFFAF6D5, /* normal_color */
306    0xFFFF948A, /* title_color */
307    0xC09E4137, /* bg_dark_color */
308    0xC0B34B41, /* bg_light_color */
309    0xC0BF5E58, /* border_dark_color */
310    0xC0F27A6F, /* border_light_color */
311    0xC01F0C0A, /* shadow_color */
312    0xC0F75431  /* particle_color */
313 };
314 
315 static const rgui_theme_t rgui_theme_opaque_legacy_red = {
316    0xFFFFBDBD, /* hover_color */
317    0xFFFAF6D5, /* normal_color */
318    0xFFFF948A, /* title_color */
319    0xFF7A3629, /* bg_dark_color */
320    0xFF963636, /* bg_light_color */
321    0xFF964444, /* border_dark_color */
322    0xFFCC5F52, /* border_light_color */
323    0xFF0E0000, /* shadow_color */
324    0xFFCC4429  /* particle_color */
325 };
326 
327 static const rgui_theme_t rgui_theme_dark_purple = {
328    0xFFF2B5D6, /* hover_color */
329    0xFFE8D0CC, /* normal_color */
330    0xFFC79FC2, /* title_color */
331    0xC0562D56, /* bg_dark_color */
332    0xC0663A66, /* bg_light_color */
333    0xC0885783, /* border_dark_color */
334    0xC0A675A1, /* border_light_color */
335    0xC0140A14, /* shadow_color */
336    0xC09786A0  /* particle_color */
337 };
338 
339 static const rgui_theme_t rgui_theme_opaque_dark_purple = {
340    0xFFF2B5D6, /* hover_color */
341    0xFFE8D0CC, /* normal_color */
342    0xFFC79FC2, /* title_color */
343    0xFF441B44, /* bg_dark_color */
344    0xFF522952, /* bg_light_color */
345    0xFF6D446D, /* border_dark_color */
346    0xFF885F88, /* border_light_color */
347    0xFF0E000E, /* shadow_color */
348    0xFF7A6D88  /* particle_color */
349 };
350 
351 static const rgui_theme_t rgui_theme_midnight_blue = {
352    0xFFB2D3ED, /* hover_color */
353    0xFFD3DCDE, /* normal_color */
354    0xFF86A1BA, /* title_color */
355    0xC024374A, /* bg_dark_color */
356    0xC03C4D5E, /* bg_light_color */
357    0xC046586A, /* border_dark_color */
358    0xC06D7F91, /* border_light_color */
359    0xC00A0F14, /* shadow_color */
360    0xC084849E  /* particle_color */
361 };
362 
363 static const rgui_theme_t rgui_theme_opaque_midnight_blue = {
364    0xFFB2D3ED, /* hover_color */
365    0xFFD3DCDE, /* normal_color */
366    0xFF86A1BA, /* title_color */
367    0xFF1B2936, /* bg_dark_color */
368    0xFF293644, /* bg_light_color */
369    0xFF364452, /* border_dark_color */
370    0xFF525F7A, /* border_light_color */
371    0xFF00000E, /* shadow_color */
372    0xFF6D6D7A  /* particle_color */
373 };
374 
375 static const rgui_theme_t rgui_theme_golden = {
376    0xFFFFE666, /* hover_color */
377    0xFFFFFFDC, /* normal_color */
378    0xFFFFCC00, /* title_color */
379    0xC0B88D0B, /* bg_dark_color */
380    0xC0BF962B, /* bg_light_color */
381    0xC0E1AD21, /* border_dark_color */
382    0xC0FCC717, /* border_light_color */
383    0xC0382B03, /* shadow_color */
384    0xC0F7D15E  /* particle_color */
385 };
386 
387 static const rgui_theme_t rgui_theme_opaque_golden = {
388    0xFFFFE666, /* hover_color */
389    0xFFFFFFDC, /* normal_color */
390    0xFFFFCC00, /* title_color */
391    0xFF966D00, /* bg_dark_color */
392    0xFF967A1B, /* bg_light_color */
393    0xFFBE881B, /* border_dark_color */
394    0xFFCCA30E, /* border_light_color */
395    0xFF291B00, /* shadow_color */
396    0xFFCCB144  /* particle_color */
397 };
398 
399 static const rgui_theme_t rgui_theme_electric_blue = {
400    0xFF7DF9FF, /* hover_color */
401    0xFFDBE9F4, /* normal_color */
402    0xFF86CDE0, /* title_color */
403    0xC02E69C6, /* bg_dark_color */
404    0xC0007FFF, /* bg_light_color */
405    0xC034A5D8, /* border_dark_color */
406    0xC070C9FF, /* border_light_color */
407    0xC012294D, /* shadow_color */
408    0xC080C7E6  /* particle_color */
409 };
410 
411 static const rgui_theme_t rgui_theme_opaque_electric_blue = {
412    0xFF7DF9FF, /* hover_color */
413    0xFFDBE9F4, /* normal_color */
414    0xFF86CDE0, /* title_color */
415    0xFF1B52A3, /* bg_dark_color */
416    0xFF0065D9, /* bg_light_color */
417    0xFF2988B1, /* border_dark_color */
418    0xFF5FA3CC, /* border_light_color */
419    0xFF0E1B36, /* shadow_color */
420    0xFF6DA3BE  /* particle_color */
421 };
422 
423 static const rgui_theme_t rgui_theme_apple_green = {
424    0xFFB0FC64, /* hover_color */
425    0xFFD8F2CB, /* normal_color */
426    0xFFA6D652, /* title_color */
427    0xC04F7942, /* bg_dark_color */
428    0xC0688539, /* bg_light_color */
429    0xC0608E3A, /* border_dark_color */
430    0xC09AB973, /* border_light_color */
431    0xC01F2E19, /* shadow_color */
432    0xC0A3C44E  /* particle_color */
433 };
434 
435 static const rgui_theme_t rgui_theme_opaque_apple_green = {
436    0xFFB0FC64, /* hover_color */
437    0xFFD8F2CB, /* normal_color */
438    0xFFA6D652, /* title_color */
439    0xFF365F36, /* bg_dark_color */
440    0xFF526D29, /* bg_light_color */
441    0xFF526D29, /* border_dark_color */
442    0xFF7A965F, /* border_light_color */
443    0xFF0E1B0E, /* shadow_color */
444    0xFF88A336  /* particle_color */
445 };
446 
447 static const rgui_theme_t rgui_theme_volcanic_red = {
448    0xFFFFCC99, /* hover_color */
449    0xFFD3D3D3, /* normal_color */
450    0xFFDDADAF, /* title_color */
451    0xC0922724, /* bg_dark_color */
452    0xC0BD0F1E, /* bg_light_color */
453    0xC0CE2029, /* border_dark_color */
454    0xC0FF0000, /* border_light_color */
455    0xC0330D0D, /* shadow_color */
456    0xC0E67D45  /* particle_color */
457 };
458 
459 static const rgui_theme_t rgui_theme_opaque_volcanic_red = {
460    0xFFFFCC99, /* hover_color */
461    0xFFD3D3D3, /* normal_color */
462    0xFFDDADAF, /* title_color */
463    0xFF7A1B1B, /* bg_dark_color */
464    0xFF96000E, /* bg_light_color */
465    0xFFA31B1B, /* border_dark_color */
466    0xFFCC0000, /* border_light_color */
467    0xFF290000, /* shadow_color */
468    0xFFBE5F36  /* particle_color */
469 };
470 
471 static const rgui_theme_t rgui_theme_lagoon = {
472    0xFFBCE1EB, /* hover_color */
473    0xFFCFCFC4, /* normal_color */
474    0xFF86C7C7, /* title_color */
475    0xC0495C6B, /* bg_dark_color */
476    0xC0526778, /* bg_light_color */
477    0xC058848F, /* border_dark_color */
478    0xC060909C, /* border_light_color */
479    0xC01C2329, /* shadow_color */
480    0xC09FB1C7  /* particle_color */
481 };
482 
483 static const rgui_theme_t rgui_theme_opaque_lagoon = {
484    0xFFBCE1EB, /* hover_color */
485    0xFFCFCFC4, /* normal_color */
486    0xFF86C7C7, /* title_color */
487    0xFF364452, /* bg_dark_color */
488    0xFF44525F, /* bg_light_color */
489    0xFF446D6D, /* border_dark_color */
490    0xFF527A7A, /* border_light_color */
491    0xFF0E1B1B, /* shadow_color */
492    0xFF7A96A3  /* particle_color */
493 };
494 
495 static const rgui_theme_t rgui_theme_brogrammer = {
496    0xFF3498DB, /* hover_color */
497    0xFFECF0F1, /* normal_color */
498    0xFF2ECC71, /* title_color */
499    0xC0242424, /* bg_dark_color */
500    0xC0242424, /* bg_light_color */
501    0xC0E74C3C, /* border_dark_color */
502    0xC0E74C3C, /* border_light_color */
503    0xC0000000, /* shadow_color */
504    0xC0606060  /* particle_color */
505 };
506 
507 static const rgui_theme_t rgui_theme_opaque_brogrammer = {
508    0xFF3498DB, /* hover_color */
509    0xFFECF0F1, /* normal_color */
510    0xFF2ECC71, /* title_color */
511    0xFF1B1B1B, /* bg_dark_color */
512    0xFF1B1B1B, /* bg_light_color */
513    0xFFBE3629, /* border_dark_color */
514    0xFFBE3629, /* border_light_color */
515    0xFF000000, /* shadow_color */
516    0xFF525252  /* particle_color */
517 };
518 
519 static const rgui_theme_t rgui_theme_dracula = {
520    0xFFBD93F9, /* hover_color */
521    0xFFF8F8F2, /* normal_color */
522    0xFFFF79C6, /* title_color */
523    0xC02F3240, /* bg_dark_color */
524    0xC02F3240, /* bg_light_color */
525    0xC06272A4, /* border_dark_color */
526    0xC06272A4, /* border_light_color */
527    0xC00F0F0F, /* shadow_color */
528    0xC06272A4  /* particle_color */
529 };
530 
531 static const rgui_theme_t rgui_theme_opaque_dracula = {
532    0xFFBD93F9, /* hover_color */
533    0xFFF8F8F2, /* normal_color */
534    0xFFFF79C6, /* title_color */
535    0xFF1B2936, /* bg_dark_color */
536    0xFF1B2936, /* bg_light_color */
537    0xFF525F88, /* border_dark_color */
538    0xFF525F88, /* border_light_color */
539    0xFF000000, /* shadow_color */
540    0xFF525F88  /* particle_color */
541 };
542 
543 static const rgui_theme_t rgui_theme_fairyfloss = {
544    0xFFFFF352, /* hover_color */
545    0xFFF8F8F2, /* normal_color */
546    0xFFFFB8D1, /* title_color */
547    0xC0675F87, /* bg_dark_color */
548    0xC0675F87, /* bg_light_color */
549    0xC08077A8, /* border_dark_color */
550    0xC08077A8, /* border_light_color */
551    0xC0262433, /* shadow_color */
552    0xC0C5A3FF  /* particle_color */
553 };
554 
555 static const rgui_theme_t rgui_theme_opaque_fairyfloss = {
556    0xFFFFF352, /* hover_color */
557    0xFFF8F8F2, /* normal_color */
558    0xFFFFB8D1, /* title_color */
559    0xFF52446D, /* bg_dark_color */
560    0xFF52446D, /* bg_light_color */
561    0xFF706087, /* border_dark_color */
562    0xFF706087, /* border_light_color */
563    0xFF1B1B29, /* shadow_color */
564    0xFFA388CC  /* particle_color */
565 };
566 
567 static const rgui_theme_t rgui_theme_flatui = {
568    0xFF0A74B9, /* hover_color */
569    0xFF2C3E50, /* normal_color */
570    0xFF8E44AD, /* title_color */
571    0xE0ECF0F1, /* bg_dark_color */
572    0xE0ECF0F1, /* bg_light_color */
573    0xE095A5A6, /* border_dark_color */
574    0xE095A5A6, /* border_light_color */
575    0xE0C3DBDE, /* shadow_color */
576    0xE0B3DFFF  /* particle_color */
577 };
578 
579 static const rgui_theme_t rgui_theme_opaque_flatui = {
580    0xFF0A74B9, /* hover_color */
581    0xFF2C3E50, /* normal_color */
582    0xFF8E44AD, /* title_color */
583    0xFFDEEEEE, /* bg_dark_color */
584    0xFFDEEEEE, /* bg_light_color */
585    0xFF8F9F9F, /* border_dark_color */
586    0xFF8F9F9F, /* border_light_color */
587    0xFFBECECE, /* shadow_color */
588    0xFFAFCEEE  /* particle_color */
589 };
590 
591 static const rgui_theme_t rgui_theme_gruvbox_dark = {
592    0xFFFE8019, /* hover_color */
593    0xFFEBDBB2, /* normal_color */
594    0xFF83A598, /* title_color */
595    0xC03D3D3D, /* bg_dark_color */
596    0xC03D3D3D, /* bg_light_color */
597    0xC099897A, /* border_dark_color */
598    0xC099897A, /* border_light_color */
599    0xC0000000, /* shadow_color */
600    0xC098971A  /* particle_color */
601 };
602 
603 static const rgui_theme_t rgui_theme_opaque_gruvbox_dark = {
604    0xFFFE8019, /* hover_color */
605    0xFFEBDBB2, /* normal_color */
606    0xFF83A598, /* title_color */
607    0xFF292929, /* bg_dark_color */
608    0xFF292929, /* bg_light_color */
609    0xFF7A6D5F, /* border_dark_color */
610    0xFF7A6D5F, /* border_light_color */
611    0xFF000000, /* shadow_color */
612    0xFF7A7A0E  /* particle_color */
613 };
614 
615 static const rgui_theme_t rgui_theme_gruvbox_light = {
616    0xFFAF3A03, /* hover_color */
617    0xFF3C3836, /* normal_color */
618    0xFF076678, /* title_color */
619    0xE0FBEBC7, /* bg_dark_color */
620    0xE0FBEBC7, /* bg_light_color */
621    0xE0928374, /* border_dark_color */
622    0xE0928374, /* border_light_color */
623    0xE0D5C4A1, /* shadow_color */
624    0xE0D5C4A1  /* particle_color */
625 };
626 
627 static const rgui_theme_t rgui_theme_opaque_gruvbox_light = {
628    0xFFAF3A03, /* hover_color */
629    0xFF3C3836, /* normal_color */
630    0xFF076678, /* title_color */
631    0xFFEEDEBE, /* bg_dark_color */
632    0xFFEEDEBE, /* bg_light_color */
633    0xFF8F7F6F, /* border_dark_color */
634    0xFF8F7F6F, /* border_light_color */
635    0xFFCEBE9F, /* shadow_color */
636    0xFFCEBE9F  /* particle_color */
637 };
638 
639 static const rgui_theme_t rgui_theme_hacking_the_kernel = {
640    0xFF83FF83, /* hover_color */
641    0xFF00E000, /* normal_color */
642    0xFF00FF00, /* title_color */
643    0xC0000000, /* bg_dark_color */
644    0xC0000000, /* bg_light_color */
645    0xC0036303, /* border_dark_color */
646    0xC0036303, /* border_light_color */
647    0xC0154D2B, /* shadow_color */
648    0xC0008C00  /* particle_color */
649 };
650 
651 static const rgui_theme_t rgui_theme_opaque_hacking_the_kernel = {
652    0xFF83FF83, /* hover_color */
653    0xFF00E000, /* normal_color */
654    0xFF00FF00, /* title_color */
655    0xFF000000, /* bg_dark_color */
656    0xFF000000, /* bg_light_color */
657    0xFF005200, /* border_dark_color */
658    0xFF005200, /* border_light_color */
659    0xFF0E361B, /* shadow_color */
660    0xFF006D00  /* particle_color */
661 };
662 
663 static const rgui_theme_t rgui_theme_nord = {
664    0xFF8FBCBB, /* hover_color */
665    0xFFD8DEE9, /* normal_color */
666    0xFF81A1C1, /* title_color */
667    0xC0363C4F, /* bg_dark_color */
668    0xC0363C4F, /* bg_light_color */
669    0xC04E596E, /* border_dark_color */
670    0xC04E596E, /* border_light_color */
671    0xC0040505, /* shadow_color */
672    0xC05E81AC  /* particle_color */
673 };
674 
675 static const rgui_theme_t rgui_theme_opaque_nord = {
676    0xFF8FBCBB, /* hover_color */
677    0xFFD8DEE9, /* normal_color */
678    0xFF81A1C1, /* title_color */
679    0xFF292936, /* bg_dark_color */
680    0xFF292936, /* bg_light_color */
681    0xFF364452, /* border_dark_color */
682    0xFF364452, /* border_light_color */
683    0xFF000000, /* shadow_color */
684    0xFF446D88  /* particle_color */
685 };
686 
687 static const rgui_theme_t rgui_theme_nova = {
688    0XFF7FC1CA, /* hover_color */
689    0XFFC5D4DD, /* normal_color */
690    0XFF9A93E1, /* title_color */
691    0xC0485B66, /* bg_dark_color */
692    0xC0485B66, /* bg_light_color */
693    0xC0627985, /* border_dark_color */
694    0xC0627985, /* border_light_color */
695    0xC01E272C, /* shadow_color */
696    0xC0889BA7  /* particle_color */
697 };
698 
699 static const rgui_theme_t rgui_theme_opaque_nova = {
700    0XFF7FC1CA, /* hover_color */
701    0XFFC5D4DD, /* normal_color */
702    0XFF9A93E1, /* title_color */
703    0xFF364452, /* bg_dark_color */
704    0xFF364452, /* bg_light_color */
705    0xFF546370, /* border_dark_color */
706    0xFF546370, /* border_light_color */
707    0xFF0E1B1B, /* shadow_color */
708    0xFF6D7A88  /* particle_color */
709 };
710 
711 static const rgui_theme_t rgui_theme_one_dark = {
712    0XFF98C379, /* hover_color */
713    0XFFBBBBBB, /* normal_color */
714    0XFFD19A66, /* title_color */
715    0xC02D323B, /* bg_dark_color */
716    0xC02D323B, /* bg_light_color */
717    0xC0495162, /* border_dark_color */
718    0xC0495162, /* border_light_color */
719    0xC007080A, /* shadow_color */
720    0xC05F697A  /* particle_color */
721 };
722 
723 static const rgui_theme_t rgui_theme_opaque_one_dark = {
724    0XFF98C379, /* hover_color */
725    0XFFBBBBBB, /* normal_color */
726    0XFFD19A66, /* title_color */
727    0xFF1B2929, /* bg_dark_color */
728    0xFF1B2929, /* bg_light_color */
729    0xFF364452, /* border_dark_color */
730    0xFF364452, /* border_light_color */
731    0xFF000000, /* shadow_color */
732    0xFF44525F  /* particle_color */
733 };
734 
735 static const rgui_theme_t rgui_theme_palenight = {
736    0xFFC792EA, /* hover_color */
737    0xFFBFC7D5, /* normal_color */
738    0xFF82AAFF, /* title_color */
739    0xC02F3347, /* bg_dark_color */
740    0xC02F3347, /* bg_light_color */
741    0xC0697098, /* border_dark_color */
742    0xC0697098, /* border_light_color */
743    0xC00D0E14, /* shadow_color */
744    0xC0697098  /* particle_color */
745 };
746 
747 static const rgui_theme_t rgui_theme_opaque_palenight = {
748    0xFFC792EA, /* hover_color */
749    0xFFBFC7D5, /* normal_color */
750    0xFF82AAFF, /* title_color */
751    0xFF1B2936, /* bg_dark_color */
752    0xFF1B2936, /* bg_light_color */
753    0xFF51617A, /* border_dark_color */
754    0xFF51617A, /* border_light_color */
755    0xFF00000E, /* shadow_color */
756    0xFF525F7A  /* particle_color */
757 };
758 
759 static const rgui_theme_t rgui_theme_solarized_dark = {
760    0xFFB58900, /* hover_color */
761    0xFF839496, /* normal_color */
762    0xFF268BD2, /* title_color */
763    0xC0003542, /* bg_dark_color */
764    0xC0003542, /* bg_light_color */
765    0xC093A1A1, /* border_dark_color */
766    0xC093A1A1, /* border_light_color */
767    0xC000141A, /* shadow_color */
768    0xC0586E75  /* particle_color */
769 };
770 
771 static const rgui_theme_t rgui_theme_opaque_solarized_dark = {
772    0xFFB58900, /* hover_color */
773    0xFF839496, /* normal_color */
774    0xFF268BD2, /* title_color */
775    0xFF002936, /* bg_dark_color */
776    0xFF002936, /* bg_light_color */
777    0xFF7A8888, /* border_dark_color */
778    0xFF7A8888, /* border_light_color */
779    0xFF000E0E, /* shadow_color */
780    0xFF44525F  /* particle_color */
781 };
782 
783 static const rgui_theme_t rgui_theme_solarized_light = {
784    0xFFB58900, /* hover_color */
785    0xFF657B83, /* normal_color */
786    0xFF268BD2, /* title_color */
787    0xE0FDEDDF, /* bg_dark_color */
788    0xE0FDEDDF, /* bg_light_color */
789    0xE093A1A1, /* border_dark_color */
790    0xE093A1A1, /* border_light_color */
791    0xE0E0DBC9, /* shadow_color */
792    0xE0FFC5AD  /* particle_color */
793 };
794 
795 static const rgui_theme_t rgui_theme_opaque_solarized_light = {
796    0xFFB58900, /* hover_color */
797    0xFF657B83, /* normal_color */
798    0xFF268BD2, /* title_color */
799    0xFFEEDECE, /* bg_dark_color */
800    0xFFEEDECE, /* bg_light_color */
801    0xFF8F9F9F, /* border_dark_color */
802    0xFF8F9F9F, /* border_light_color */
803    0xFFDECEBE, /* shadow_color */
804    0xFFEEBE9F  /* particle_color */
805 };
806 
807 static const rgui_theme_t rgui_theme_tango_dark = {
808    0xFF8AE234, /* hover_color */
809    0xFFEEEEEC, /* normal_color */
810    0xFF729FCF, /* title_color */
811    0xC0384042, /* bg_dark_color */
812    0xC0384042, /* bg_light_color */
813    0xC06A767A, /* border_dark_color */
814    0xC06A767A, /* border_light_color */
815    0xC01A1A1A, /* shadow_color */
816    0xC0C4A000  /* particle_color */
817 };
818 
819 static const rgui_theme_t rgui_theme_opaque_tango_dark = {
820    0xFF8AE234, /* hover_color */
821    0xFFEEEEEC, /* normal_color */
822    0xFF729FCF, /* title_color */
823    0xFF293636, /* bg_dark_color */
824    0xFF293636, /* bg_light_color */
825    0xFF556160, /* border_dark_color */
826    0xFF556160, /* border_light_color */
827    0xFF0E0E0E, /* shadow_color */
828    0xFFA38800  /* particle_color */
829 };
830 
831 static const rgui_theme_t rgui_theme_tango_light = {
832    0xFF4E9A06, /* hover_color */
833    0xFF2E3436, /* normal_color */
834    0xFF204A87, /* title_color */
835    0xE0EEEEEC, /* bg_dark_color */
836    0xE0EEEEEC, /* bg_light_color */
837    0xE0C7C7C7, /* border_dark_color */
838    0xE0C7C7C7, /* border_light_color */
839    0xE0D3D7CF, /* shadow_color */
840    0xE0FFCA78  /* particle_color */
841 };
842 
843 static const rgui_theme_t rgui_theme_opaque_tango_light = {
844    0xFF4E9A06, /* hover_color */
845    0xFF2E3436, /* normal_color */
846    0xFF204A87, /* title_color */
847    0xFFDEDEDE, /* bg_dark_color */
848    0xFFDEDEDE, /* bg_light_color */
849    0xFFBEBEBE, /* border_dark_color */
850    0xFFBEBEBE, /* border_light_color */
851    0xFFCECEBE, /* shadow_color */
852    0xFFEEBE6F  /* particle_color */
853 };
854 
855 static const rgui_theme_t rgui_theme_zenburn = {
856    0xFFF0DFAF, /* hover_color */
857    0xFFDCDCCC, /* normal_color */
858    0xFF8FB28F, /* title_color */
859    0xC04F4F4F, /* bg_dark_color */
860    0xC04F4F4F, /* bg_light_color */
861    0xC0636363, /* border_dark_color */
862    0xC0636363, /* border_light_color */
863    0xC01F1F1F, /* shadow_color */
864    0xC0AC7373  /* particle_color */
865 };
866 
867 static const rgui_theme_t rgui_theme_opaque_zenburn = {
868    0xFFF0DFAF, /* hover_color */
869    0xFFDCDCCC, /* normal_color */
870    0xFF8FB28F, /* title_color */
871    0xFF363636, /* bg_dark_color */
872    0xFF363636, /* bg_light_color */
873    0xFF505050, /* border_dark_color */
874    0xFF505050, /* border_light_color */
875    0xFF0E0E0E, /* shadow_color */
876    0xFF885F5F  /* particle_color */
877 };
878 
879 static const rgui_theme_t rgui_theme_anti_zenburn = {
880    0xFF336C6C, /* hover_color */
881    0xFF232333, /* normal_color */
882    0xFF205070, /* title_color */
883    0xE0C0C0C0, /* bg_dark_color */
884    0xE0C0C0C0, /* bg_light_color */
885    0xE0A0A0A0, /* border_dark_color */
886    0xE0A0A0A0, /* border_light_color */
887    0xE0B0B0B0, /* shadow_color */
888    0xE0B090B0  /* particle_color */
889 };
890 
891 static const rgui_theme_t rgui_theme_opaque_anti_zenburn = {
892    0xFF336C6C, /* hover_color */
893    0xFF232333, /* normal_color */
894    0xFF205070, /* title_color */
895    0xFFBEBEBE, /* bg_dark_color */
896    0xFFBEBEBE, /* bg_light_color */
897    0xFF9F9F9F, /* border_dark_color */
898    0xFF9F9F9F, /* border_light_color */
899    0xFFAFAFAF, /* shadow_color */
900    0xFFAF8FAF  /* particle_color */
901 };
902 
903 static const rgui_theme_t rgui_theme_flux = {
904    0xFF6FCB9F, /* hover_color */
905    0xFF666547, /* normal_color */
906    0xFFFB2E01, /* title_color */
907    0xE0FFFEB3, /* bg_dark_color */
908    0xE0FFFEB3, /* bg_light_color */
909    0xE0FFE28A, /* border_dark_color */
910    0xE0FFE28A, /* border_light_color */
911    0xE0FFE28A, /* shadow_color */
912    0xE0FB2E01  /* particle_color */
913 };
914 
915 static const rgui_theme_t rgui_theme_opaque_flux = {
916    0xFF6FCB9F, /* hover_color */
917    0xFF666547, /* normal_color */
918    0xFFFB2E01, /* title_color */
919    0xFFEEEEAF, /* bg_dark_color */
920    0xFFEEEEAF, /* bg_light_color */
921    0xFFEEDE7F, /* border_dark_color */
922    0xFFEEDE7F, /* border_light_color */
923    0xFFEEDE7F, /* shadow_color */
924    0xFFEE2000  /* particle_color */
925 };
926 
927 typedef struct
928 {
929    uint16_t hover_color;
930    uint16_t normal_color;
931    uint16_t title_color;
932    uint16_t bg_dark_color;
933    uint16_t bg_light_color;
934    uint16_t border_dark_color;
935    uint16_t border_light_color;
936    uint16_t shadow_color;
937    uint16_t particle_color;
938    /* Screensaver colors */
939    uint16_t ss_bg_color;
940    uint16_t ss_particle_color;
941 } rgui_colors_t;
942 
943 typedef struct
944 {
945    unsigned start_x;
946    unsigned start_y;
947    unsigned width;
948    unsigned height;
949    unsigned value_maxlen;
950 } rgui_term_layout_t;
951 
952 typedef struct
953 {
954    video_viewport_t viewport; /* int alignment */
955    unsigned aspect_ratio_idx;
956 } rgui_video_settings_t;
957 
958 /* A 'particle' is just 4 float variables that can
959  * be used for any purpose - e.g.:
960  * > a = x pos
961  * > b = y pos
962  * > c = x velocity
963  * or:
964  * > a = radius
965  * > b = theta
966  * etc. */
967 typedef struct
968 {
969    float a;
970    float b;
971    float c;
972    float d;
973 } rgui_particle_t;
974 
975 /* Defines all possible entry value types
976  * > Note: These are not necessarily 'values',
977  *   but they correspond to the object drawn in
978  *   the 'value' location when rendering
979  *   menu lists */
980 enum rgui_entry_value_type
981 {
982    RGUI_ENTRY_VALUE_NONE = 0,
983    RGUI_ENTRY_VALUE_TEXT,
984    RGUI_ENTRY_VALUE_SWITCH_ON,
985    RGUI_ENTRY_VALUE_SWITCH_OFF,
986    RGUI_ENTRY_VALUE_CHECKMARK
987 };
988 
989 typedef struct
990 {
991    uint16_t *data;
992    unsigned max_width;
993    unsigned max_height;
994    unsigned width;
995    unsigned height;
996    char path[PATH_MAX_LENGTH];
997    bool is_valid;
998 } thumbnail_t;
999 
1000 typedef struct
1001 {
1002    uint16_t *data;
1003    unsigned width;
1004    unsigned height;
1005 } frame_buf_t;
1006 
1007 typedef struct
1008 {
1009    retro_time_t thumbnail_load_trigger_time; /* uint64_t */
1010 
1011    gfx_thumbnail_path_data_t *thumbnail_path_data;
1012 
1013    struct
1014    {
1015       bitmapfont_lut_t *regular;
1016       bitmapfont_lut_t *eng_10x10;
1017       bitmapfont_lut_t *chn_10x10;
1018       bitmapfont_lut_t *jpn_10x10;
1019       bitmapfont_lut_t *kor_10x10;
1020       bitmapfont_lut_t *rus_10x10;
1021    } fonts;
1022 
1023    frame_buf_t frame_buf;
1024    frame_buf_t background_buf;
1025    frame_buf_t upscale_buf;
1026 
1027    thumbnail_t fs_thumbnail;
1028    thumbnail_t mini_thumbnail;
1029    thumbnail_t mini_left_thumbnail;
1030 
1031    rgui_video_settings_t menu_video_settings;      /* int alignment */
1032    rgui_video_settings_t content_video_settings;   /* int alignment */
1033 
1034    unsigned font_width;
1035    unsigned font_height;
1036    unsigned font_width_stride;
1037    unsigned font_height_stride;
1038 
1039    unsigned mini_thumbnail_max_width;
1040    unsigned mini_thumbnail_max_height;
1041    unsigned last_width;
1042    unsigned last_height;
1043    unsigned window_width;
1044    unsigned window_height;
1045    unsigned particle_effect;
1046    unsigned color_theme;
1047    unsigned menu_aspect_ratio;
1048    unsigned menu_aspect_ratio_lock;
1049    unsigned language;
1050 
1051    rgui_term_layout_t term_layout;
1052 
1053    uint32_t thumbnail_queue_size;
1054    uint32_t left_thumbnail_queue_size;
1055 
1056    rgui_particle_t particles[RGUI_NUM_PARTICLES]; /* float alignment */
1057 
1058    int16_t scroll_y;
1059    rgui_colors_t colors;   /* int16_t alignment */
1060 
1061    struct scaler_ctx image_scaler;
1062    menu_input_pointer_t pointer;
1063 
1064    char msgbox[1024];
1065    char theme_preset_path[PATH_MAX_LENGTH]; /* Must be a fixed length array... */
1066    char menu_title[255]; /* Must be a fixed length array... */
1067    char menu_sublabel[MENU_SUBLABEL_MAX_LENGTH]; /* Must be a fixed length array... */
1068 
1069    bool bg_modified;
1070    bool force_redraw;
1071    bool force_menu_refresh;
1072    bool restore_aspect_lock;
1073    bool show_mouse;
1074    bool show_screensaver;
1075    bool ignore_resize_events;
1076    bool bg_thickness;
1077    bool border_thickness;
1078    bool border_enable;
1079    bool transparency_supported;
1080    bool transparency_enable;
1081    bool shadow_enable;
1082    bool extended_ascii_enable;
1083    bool is_playlist;
1084    bool entry_has_thumbnail;
1085    bool entry_has_left_thumbnail;
1086    bool show_fs_thumbnail;
1087    bool thumbnail_load_pending;
1088    bool show_wallpaper;
1089    bool aspect_update_pending;
1090 #ifdef HAVE_GFX_WIDGETS
1091    bool widgets_supported;
1092 #endif
1093 } rgui_t;
1094 
1095 /* Particle effect animations update at a base rate
1096  * of 60Hz (-> 16.666 ms update period) */
1097 static const float particle_effect_period = (1.0f / 60.0f) * 1000.0f;
1098 
1099 /* ==============================
1100  * Custom Symbols (glyphs) START
1101  * ============================== */
1102 
1103 #define RGUI_SYMBOL_WIDTH         FONT_WIDTH
1104 #define RGUI_SYMBOL_HEIGHT        FONT_HEIGHT
1105 #define RGUI_SYMBOL_WIDTH_STRIDE  (RGUI_SYMBOL_WIDTH + 1)
1106 #define RGUI_SYMBOL_HEIGHT_STRIDE (RGUI_SYMBOL_HEIGHT + 1)
1107 
1108 enum rgui_symbol_type
1109 {
1110    RGUI_SYMBOL_BACKSPACE = 0,
1111    RGUI_SYMBOL_ENTER,
1112    RGUI_SYMBOL_SHIFT_UP,
1113    RGUI_SYMBOL_SHIFT_DOWN,
1114    RGUI_SYMBOL_NEXT,
1115    RGUI_SYMBOL_TEXT_CURSOR,
1116    RGUI_SYMBOL_CHARGING,
1117    RGUI_SYMBOL_BATTERY_100,
1118    RGUI_SYMBOL_BATTERY_80,
1119    RGUI_SYMBOL_BATTERY_60,
1120    RGUI_SYMBOL_BATTERY_40,
1121    RGUI_SYMBOL_BATTERY_20,
1122    RGUI_SYMBOL_CHECKMARK,
1123    RGUI_SYMBOL_SWITCH_ON_LEFT,
1124    RGUI_SYMBOL_SWITCH_ON_CENTRE,
1125    RGUI_SYMBOL_SWITCH_ON_RIGHT,
1126    RGUI_SYMBOL_SWITCH_OFF_LEFT,
1127    RGUI_SYMBOL_SWITCH_OFF_CENTRE,
1128    RGUI_SYMBOL_SWITCH_OFF_RIGHT
1129 };
1130 
1131 /* All custom symbols must have dimensions
1132  * of exactly RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT */
1133 static const uint8_t rgui_symbol_data_backspace[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1134       0, 0, 0, 0, 0,
1135       0, 0, 0, 0, 0,
1136       0, 0, 1, 0, 0,
1137       0, 1, 0, 0, 0,
1138       1, 1, 1, 1, 1,
1139       0, 1, 0, 0, 0,
1140       0, 0, 1, 0, 0,
1141       0, 0, 0, 0, 0, /* Baseline */
1142       0, 0, 0, 0, 0,
1143       0, 0, 0, 0, 0};
1144 
1145 static const uint8_t rgui_symbol_data_enter[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1146       0, 0, 0, 0, 0,
1147       0, 0, 0, 0, 1,
1148       0, 0, 0, 0, 1,
1149       0, 0, 0, 0, 1,
1150       0, 0, 1, 0, 1,
1151       0, 1, 0, 0, 1,
1152       1, 1, 1, 1, 1,
1153       0, 1, 0, 0, 0, /* Baseline */
1154       0, 0, 1, 0, 0,
1155       0, 0, 0, 0, 0};
1156 
1157 static const uint8_t rgui_symbol_data_shift_up[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1158       0, 0, 0, 0, 0,
1159       0, 0, 1, 0, 0,
1160       0, 1, 1, 1, 0,
1161       1, 1, 0, 1, 1,
1162       0, 1, 0, 1, 0,
1163       0, 1, 0, 1, 0,
1164       0, 1, 0, 1, 0,
1165       0, 1, 1, 1, 0, /* Baseline */
1166       0, 0, 0, 0, 0,
1167       0, 0, 0, 0, 0};
1168 
1169 static const uint8_t rgui_symbol_data_shift_down[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1170       0, 0, 0, 0, 0,
1171       0, 1, 1, 1, 0,
1172       0, 1, 0, 1, 0,
1173       0, 1, 0, 1, 0,
1174       0, 1, 0, 1, 0,
1175       1, 1, 0, 1, 1,
1176       0, 1, 1, 1, 0,
1177       0, 0, 1, 0, 0, /* Baseline */
1178       0, 0, 0, 0, 0,
1179       0, 0, 0, 0, 0};
1180 
1181 static const uint8_t rgui_symbol_data_next[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1182       0, 0, 0, 0, 0,
1183       0, 0, 0, 0, 0,
1184       0, 1, 1, 1, 0,
1185       1, 0, 1, 0, 1,
1186       1, 1, 1, 1, 1,
1187       1, 0, 1, 0, 1,
1188       0, 1, 1, 1, 0,
1189       0, 0, 0, 0, 0, /* Baseline */
1190       0, 0, 0, 0, 0,
1191       0, 0, 0, 0, 0};
1192 
1193 static const uint8_t rgui_symbol_data_text_cursor[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1194       1, 1, 1, 1, 1,
1195       1, 1, 1, 1, 1,
1196       1, 1, 1, 1, 1,
1197       1, 1, 1, 1, 1,
1198       1, 1, 1, 1, 1,
1199       1, 1, 1, 1, 1,
1200       1, 1, 1, 1, 1,
1201       1, 1, 1, 1, 1, /* Baseline */
1202       1, 1, 1, 1, 1,
1203       1, 1, 1, 1, 1};
1204 
1205 static const uint8_t rgui_symbol_data_charging[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1206       0, 0, 0, 0, 0,
1207       0, 1, 0, 1, 0,
1208       0, 1, 0, 1, 0,
1209       1, 1, 1, 1, 1,
1210       1, 1, 1, 1, 1,
1211       0, 1, 1, 1, 0,
1212       0, 0, 1, 0, 0,
1213       0, 0, 1, 0, 0, /* Baseline */
1214       0, 0, 0, 0, 0,
1215       0, 0, 0, 0, 0};
1216 
1217 static const uint8_t rgui_symbol_data_battery_100[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1218       0, 0, 0, 0, 0,
1219       0, 0, 1, 1, 0,
1220       0, 1, 1, 1, 1,
1221       0, 1, 1, 1, 1,
1222       0, 1, 1, 1, 1,
1223       0, 1, 1, 1, 1,
1224       0, 1, 1, 1, 1,
1225       0, 1, 1, 1, 1, /* Baseline */
1226       0, 0, 0, 0, 0,
1227       0, 0, 0, 0, 0};
1228 
1229 static const uint8_t rgui_symbol_data_battery_80[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1230       0, 0, 0, 0, 0,
1231       0, 0, 1, 1, 0,
1232       0, 1, 1, 1, 1,
1233       0, 1, 0, 0, 1,
1234       0, 1, 1, 1, 1,
1235       0, 1, 1, 1, 1,
1236       0, 1, 1, 1, 1,
1237       0, 1, 1, 1, 1, /* Baseline */
1238       0, 0, 0, 0, 0,
1239       0, 0, 0, 0, 0};
1240 
1241 static const uint8_t rgui_symbol_data_battery_60[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1242       0, 0, 0, 0, 0,
1243       0, 0, 1, 1, 0,
1244       0, 1, 1, 1, 1,
1245       0, 1, 0, 0, 1,
1246       0, 1, 0, 0, 1,
1247       0, 1, 1, 1, 1,
1248       0, 1, 1, 1, 1,
1249       0, 1, 1, 1, 1, /* Baseline */
1250       0, 0, 0, 0, 0,
1251       0, 0, 0, 0, 0};
1252 
1253 static const uint8_t rgui_symbol_data_battery_40[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1254       0, 0, 0, 0, 0,
1255       0, 0, 1, 1, 0,
1256       0, 1, 1, 1, 1,
1257       0, 1, 0, 0, 1,
1258       0, 1, 0, 0, 1,
1259       0, 1, 0, 0, 1,
1260       0, 1, 1, 1, 1,
1261       0, 1, 1, 1, 1, /* Baseline */
1262       0, 0, 0, 0, 0,
1263       0, 0, 0, 0, 0};
1264 
1265 static const uint8_t rgui_symbol_data_battery_20[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1266       0, 0, 0, 0, 0,
1267       0, 0, 1, 1, 0,
1268       0, 1, 1, 1, 1,
1269       0, 1, 0, 0, 1,
1270       0, 1, 0, 0, 1,
1271       0, 1, 0, 0, 1,
1272       0, 1, 0, 0, 1,
1273       0, 1, 1, 1, 1, /* Baseline */
1274       0, 0, 0, 0, 0,
1275       0, 0, 0, 0, 0};
1276 
1277 /* Note: This is not actually a 'checkmark' - we don't
1278  * have enough pixels to draw one effectively. The 'icon'
1279  * is merely named according to its function... */
1280 static const uint8_t rgui_symbol_data_checkmark[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1281       0, 1, 1, 0, 0,
1282       0, 1, 1, 0, 0,
1283       0, 1, 1, 0, 0,
1284       0, 1, 1, 0, 0,
1285       0, 1, 1, 0, 0,
1286       0, 1, 1, 0, 0,
1287       0, 1, 1, 0, 0,
1288       0, 1, 1, 0, 0, /* Baseline */
1289       0, 1, 1, 0, 0,
1290       0, 0, 0, 0, 0};
1291 
1292 static const uint8_t rgui_symbol_data_switch_on_left[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1293       0, 0, 0, 0, 0,
1294       0, 0, 0, 0, 0,
1295       0, 0, 0, 0, 0,
1296       0, 1, 1, 1, 1,
1297       1, 1, 1, 1, 1,
1298       0, 1, 1, 1, 1,
1299       0, 0, 0, 0, 0,
1300       0, 0, 0, 0, 0, /* Baseline */
1301       0, 0, 0, 0, 0,
1302       0, 0, 0, 0, 0};
1303 
1304 static const uint8_t rgui_symbol_data_switch_on_centre[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1305       0, 0, 0, 0, 0,
1306       0, 0, 0, 0, 0,
1307       0, 0, 0, 0, 0,
1308       1, 1, 1, 1, 0,
1309       1, 1, 1, 1, 0,
1310       1, 1, 1, 1, 0,
1311       0, 0, 0, 0, 0,
1312       0, 0, 0, 0, 0, /* Baseline */
1313       0, 0, 0, 0, 0,
1314       0, 0, 0, 0, 0};
1315 
1316 static const uint8_t rgui_symbol_data_switch_on_right[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1317       0, 0, 0, 0, 0,
1318       0, 1, 1, 1, 0,
1319       1, 1, 1, 1, 1,
1320       1, 1, 1, 1, 1,
1321       1, 1, 1, 1, 1,
1322       1, 1, 1, 1, 1,
1323       1, 1, 1, 1, 1,
1324       0, 1, 1, 1, 0, /* Baseline */
1325       0, 0, 0, 0, 0,
1326       0, 0, 0, 0, 0};
1327 
1328 static const uint8_t rgui_symbol_data_switch_off_left[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1329       0, 0, 0, 0, 0,
1330       0, 1, 1, 1, 0,
1331       1, 0, 0, 0, 1,
1332       1, 0, 0, 0, 1,
1333       1, 0, 0, 0, 1,
1334       1, 0, 0, 0, 1,
1335       1, 0, 0, 0, 1,
1336       0, 1, 1, 1, 0, /* Baseline */
1337       0, 0, 0, 0, 0,
1338       0, 0, 0, 0, 0};
1339 
1340 static const uint8_t rgui_symbol_data_switch_off_centre[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1341       0, 0, 0, 0, 0,
1342       0, 0, 0, 0, 0,
1343       0, 0, 0, 0, 0,
1344       0, 1, 1, 1, 1,
1345       0, 1, 0, 0, 0,
1346       0, 1, 1, 1, 1,
1347       0, 0, 0, 0, 0,
1348       0, 0, 0, 0, 0, /* Baseline */
1349       0, 0, 0, 0, 0,
1350       0, 0, 0, 0, 0};
1351 
1352 static const uint8_t rgui_symbol_data_switch_off_right[RGUI_SYMBOL_WIDTH * RGUI_SYMBOL_HEIGHT] = {
1353       0, 0, 0, 0, 0,
1354       0, 0, 0, 0, 0,
1355       0, 0, 0, 0, 0,
1356       1, 1, 1, 1, 0,
1357       0, 0, 0, 0, 1,
1358       1, 1, 1, 1, 0,
1359       0, 0, 0, 0, 0,
1360       0, 0, 0, 0, 0, /* Baseline */
1361       0, 0, 0, 0, 0,
1362       0, 0, 0, 0, 0};
1363 
1364 /* ==============================
1365  * Custom Symbols (glyphs) END
1366  * ============================== */
1367 
1368 /* ==============================
1369  * pixel format conversion START
1370  * ============================== */
1371 
1372 /* PS2 */
argb32_to_abgr1555(uint32_t col)1373 static uint16_t argb32_to_abgr1555(uint32_t col)
1374 {
1375    /* Extract colour components */
1376    unsigned a = (col >> 24) & 0xFF;
1377    unsigned r = (col >> 16) & 0xFF;
1378    unsigned g = (col >> 8)  & 0xFF;
1379    unsigned b =  col        & 0xFF;
1380    if (a < 0xFF)
1381    {
1382       /* Background and border colours are normally semi-transparent
1383        * (so we can see suspended content when opening the quick menu).
1384        * When no content is loaded, the 'image' behind the RGUI background
1385        * and border is black - which has the effect of darkening the
1386        * perceived background/border colours. All the preset theme (and
1387        * default 'custom') colour values have been adjusted to account for
1388        * this, but abgr1555 only has a 1 bit alpha channel. This means all
1389        * colours become fully opaque, and consequently backgrounds/borders
1390        * become abnormally bright.
1391        * We therefore have to darken each RGB value according to the alpha
1392        * component of the input colour... */
1393       float a_factor = (float)a * (1.0f / 255.0f);
1394       r = (unsigned)(((float)r * a_factor) + 0.5f) & 0xFF;
1395       g = (unsigned)(((float)g * a_factor) + 0.5f) & 0xFF;
1396       b = (unsigned)(((float)b * a_factor) + 0.5f) & 0xFF;
1397    }
1398    /* Convert from 8 bit to 5 bit */
1399    r = r >> 3;
1400    g = g >> 3;
1401    b = b >> 3;
1402    /* Return final value - alpha always set to 1 */
1403    return (1 << 15) | (b << 10) | (g << 5) | r;
1404 }
1405 
1406 /* GEKKO */
argb32_to_rgb5a3(uint32_t col)1407 static uint16_t argb32_to_rgb5a3(uint32_t col)
1408 {
1409    /* Extract colour components */
1410    unsigned a  = (col >> 24) & 0xFF;
1411    unsigned r  = (col >> 16) & 0xFF;
1412    unsigned g  = (col >> 8)  & 0xFF;
1413    unsigned b  =  col        & 0xFF;
1414    unsigned a3 =  a   >> 5;
1415    if (a < 0xFF)
1416    {
1417       /* Gekko platforms only have a 3 bit alpha channel, which
1418        * is one bit less than all 'standard' target platforms.
1419        * As a result, Gekko colours are effectively ~6-7% less
1420        * transparent than expected, which causes backgrounds and
1421        * borders to appear too bright. We therefore have to darken
1422        * each RGB component according to the difference between Gekko
1423        * alpha and normal 4 bit alpha values... */
1424       unsigned a4    = a >> 4;
1425       float a_factor = 1.0f;
1426       if (a3 > 0)
1427       {
1428          /* Avoid divide by zero errors... */
1429          a_factor = ((float)a4 * (1.0f / 15.0f)) / ((float)a3 * (1.0f / 7.0f));
1430       }
1431       r = (unsigned)(((float)r * a_factor) + 0.5f);
1432       g = (unsigned)(((float)g * a_factor) + 0.5f);
1433       b = (unsigned)(((float)b * a_factor) + 0.5f);
1434       /* a_factor can actually be greater than 1. This will never happen
1435        * with the current preset theme colour values, but users can set
1436        * any custom values they like, so we have to play it safe... */
1437       r = (r <= 0xFF) ? r : 0xFF;
1438       g = (g <= 0xFF) ? g : 0xFF;
1439       b = (b <= 0xFF) ? b : 0xFF;
1440    }
1441    /* Convert RGB from 8 bit to 4 bit */
1442    r = r >> 4;
1443    g = g >> 4;
1444    b = b >> 4;
1445    /* Return final value */
1446    return (a3 << 12) | (r << 8) | (g << 4) | b;
1447 }
1448 
1449 /* PSP */
argb32_to_abgr4444(uint32_t col)1450 static uint16_t argb32_to_abgr4444(uint32_t col)
1451 {
1452    unsigned a = ((col >> 24) & 0xFF) >> 4;
1453    unsigned r = ((col >> 16) & 0xFF) >> 4;
1454    unsigned g = ((col >> 8)  & 0xFF) >> 4;
1455    unsigned b = ( col        & 0xFF) >> 4;
1456    return (a << 12) | (b << 8) | (g << 4) | r;
1457 }
1458 
1459 /* D3D10/11/12 */
argb32_to_bgra4444(uint32_t col)1460 static uint16_t argb32_to_bgra4444(uint32_t col)
1461 {
1462    unsigned a = ((col >> 24) & 0xFF) >> 4;
1463    unsigned r = ((col >> 16) & 0xFF) >> 4;
1464    unsigned g = ((col >> 8)  & 0xFF) >> 4;
1465    unsigned b = ( col        & 0xFF) >> 4;
1466    return (b << 12) | (g << 8) | (r << 4) | a;
1467 }
1468 
1469 /* DINGUX SDL */
argb32_to_rgb565(uint32_t col)1470 static uint16_t argb32_to_rgb565(uint32_t col)
1471 {
1472    /* Extract colour components */
1473    unsigned a = (col >> 24) & 0xFF;
1474    unsigned r = (col >> 16) & 0xFF;
1475    unsigned g = (col >> 8)  & 0xFF;
1476    unsigned b =  col        & 0xFF;
1477    if (a < 0xFF)
1478    {
1479       /* RGB565 has no alpha component - as with PS2 colour conversion,
1480        * have to darken each RGB value according to the alpha component
1481        * of the input colour... */
1482       float a_factor = (float)a * (1.0f / 255.0f);
1483       r = (unsigned)(((float)r * a_factor) + 0.5f) & 0xFF;
1484       g = (unsigned)(((float)g * a_factor) + 0.5f) & 0xFF;
1485       b = (unsigned)(((float)b * a_factor) + 0.5f) & 0xFF;
1486    }
1487    /* Convert from 8 bit to 5 bit */
1488    r = r >> 3;
1489    g = g >> 3;
1490    b = b >> 3;
1491    /* Return final value */
1492    return (r << 11) | (g << 6) | b;
1493 }
1494 
1495 /* All other platforms */
argb32_to_rgba4444(uint32_t col)1496 static uint16_t argb32_to_rgba4444(uint32_t col)
1497 {
1498    unsigned a = ((col >> 24) & 0xFF) >> 4;
1499    unsigned r = ((col >> 16) & 0xFF) >> 4;
1500    unsigned g = ((col >> 8)  & 0xFF) >> 4;
1501    unsigned b = ( col        & 0xFF) >> 4;
1502    return (r << 12) | (g << 8) | (b << 4) | a;
1503 }
1504 
1505 static uint16_t (*argb32_to_pixel_platform_format)(uint32_t col) = argb32_to_rgba4444;
1506 
1507 /* Returns true if current pixel format supports
1508  * framebuffer transparency */
rgui_set_pixel_format_function(void)1509 static bool rgui_set_pixel_format_function(void)
1510 {
1511    const char *driver_ident    = video_driver_get_ident();
1512    bool transparency_supported = true;
1513 
1514    /* Default fallback... */
1515    if (string_is_empty(driver_ident))
1516    {
1517       argb32_to_pixel_platform_format = argb32_to_rgba4444;
1518       return transparency_supported;
1519    }
1520 
1521    if (     string_is_equal(driver_ident, "ps2"))             /* PS2 */
1522    {
1523       argb32_to_pixel_platform_format = argb32_to_abgr1555;
1524       transparency_supported          = false;
1525    }
1526    else if (string_is_equal(driver_ident, "gx"))              /* GEKKO */
1527       argb32_to_pixel_platform_format = argb32_to_rgb5a3;
1528    else if (string_is_equal(driver_ident, "psp1"))            /* PSP */
1529       argb32_to_pixel_platform_format = argb32_to_abgr4444;
1530    else if (string_is_equal(driver_ident, "d3d10") ||         /* D3D10/11/12 */
1531             string_is_equal(driver_ident, "d3d11") ||
1532             string_is_equal(driver_ident, "d3d12"))
1533       argb32_to_pixel_platform_format = argb32_to_bgra4444;
1534    else if (string_is_equal(driver_ident, "sdl_dingux") ||    /* DINGUX SDL */
1535             string_is_equal(driver_ident, "sdl_rs90"))
1536    {
1537       argb32_to_pixel_platform_format = argb32_to_rgb565;
1538       transparency_supported          = false;
1539    }
1540    else
1541       argb32_to_pixel_platform_format = argb32_to_rgba4444;
1542 
1543    return transparency_supported;
1544 }
1545 
1546 /* ==============================
1547  * pixel format conversion END
1548  * ============================== */
1549 
rgui_fonts_free(rgui_t * rgui)1550 static void rgui_fonts_free(rgui_t *rgui)
1551 {
1552    if (!rgui)
1553       return;
1554 
1555    if (rgui->fonts.regular)
1556    {
1557       bitmapfont_free_lut(rgui->fonts.regular);
1558       rgui->fonts.regular = NULL;
1559    }
1560 
1561    if (rgui->fonts.eng_10x10)
1562    {
1563       bitmapfont_free_lut(rgui->fonts.eng_10x10);
1564       rgui->fonts.eng_10x10 = NULL;
1565    }
1566 
1567    if (rgui->fonts.chn_10x10)
1568    {
1569       bitmapfont_free_lut(rgui->fonts.chn_10x10);
1570       rgui->fonts.chn_10x10 = NULL;
1571    }
1572 
1573    if (rgui->fonts.jpn_10x10)
1574    {
1575       bitmapfont_free_lut(rgui->fonts.jpn_10x10);
1576       rgui->fonts.jpn_10x10 = NULL;
1577    }
1578 
1579    if (rgui->fonts.kor_10x10)
1580    {
1581       bitmapfont_free_lut(rgui->fonts.kor_10x10);
1582       rgui->fonts.kor_10x10 = NULL;
1583    }
1584 
1585    if (rgui->fonts.rus_10x10)
1586    {
1587       bitmapfont_free_lut(rgui->fonts.rus_10x10);
1588       rgui->fonts.rus_10x10 = NULL;
1589    }
1590 }
1591 
rgui_fonts_init(rgui_t * rgui)1592 static bool rgui_fonts_init(rgui_t *rgui)
1593 {
1594 #ifdef HAVE_LANGEXTRA
1595    unsigned language = *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE);
1596 
1597    switch (language)
1598    {
1599       case RETRO_LANGUAGE_ENGLISH:
1600          goto english;
1601       case RETRO_LANGUAGE_FRENCH:
1602       case RETRO_LANGUAGE_SPANISH:
1603       case RETRO_LANGUAGE_GERMAN:
1604       case RETRO_LANGUAGE_ITALIAN:
1605       case RETRO_LANGUAGE_DUTCH:
1606       case RETRO_LANGUAGE_PORTUGUESE_BRAZIL:
1607       case RETRO_LANGUAGE_PORTUGUESE_PORTUGAL:
1608       case RETRO_LANGUAGE_ESPERANTO:
1609       case RETRO_LANGUAGE_POLISH:
1610       case RETRO_LANGUAGE_VIETNAMESE:
1611       case RETRO_LANGUAGE_TURKISH:
1612       case RETRO_LANGUAGE_SLOVAK:
1613       case RETRO_LANGUAGE_ASTURIAN:
1614          /* We have at least partial support for
1615           * these languages, but extended ASCII
1616           * is required */
1617          {
1618             settings_t *settings  = config_get_ptr();
1619             configuration_set_bool(settings,
1620                   settings->bools.menu_rgui_extended_ascii, true);
1621             rgui->extended_ascii_enable = true;
1622             rgui->language              = language;
1623             goto english;
1624          }
1625       case RETRO_LANGUAGE_JAPANESE:
1626       case RETRO_LANGUAGE_KOREAN:
1627       case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
1628       case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
1629          rgui->fonts.eng_10x10    = bitmapfont_10x10_load(RETRO_LANGUAGE_ENGLISH);
1630          rgui->fonts.chn_10x10    = bitmapfont_10x10_load(RETRO_LANGUAGE_CHINESE_SIMPLIFIED);
1631          rgui->fonts.jpn_10x10    = bitmapfont_10x10_load(RETRO_LANGUAGE_JAPANESE);
1632          rgui->fonts.kor_10x10    = bitmapfont_10x10_load(RETRO_LANGUAGE_KOREAN);
1633 
1634          if (!rgui->fonts.eng_10x10 ||
1635              !rgui->fonts.chn_10x10 ||
1636              !rgui->fonts.jpn_10x10 ||
1637              !rgui->fonts.kor_10x10)
1638          {
1639             rgui_fonts_free(rgui);
1640             *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE) = RETRO_LANGUAGE_ENGLISH;
1641             runloop_msg_queue_push(
1642                   msg_hash_to_str(MSG_RGUI_MISSING_FONTS), 1, 256, false, NULL,
1643                   MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1644             goto english;
1645          }
1646 
1647          rgui->font_width         = FONT_10X10_WIDTH;
1648          rgui->font_height        = FONT_10X10_HEIGHT;
1649          rgui->font_width_stride  = FONT_10X10_WIDTH_STRIDE;
1650          rgui->font_height_stride = FONT_10X10_HEIGHT_STRIDE;
1651          rgui->language           = language;
1652          break;
1653       case RETRO_LANGUAGE_RUSSIAN:
1654          rgui->fonts.eng_10x10    = bitmapfont_10x10_load(RETRO_LANGUAGE_ENGLISH);
1655          rgui->fonts.rus_10x10    = bitmapfont_10x10_load(RETRO_LANGUAGE_RUSSIAN);
1656 
1657          if (!rgui->fonts.eng_10x10 ||
1658              !rgui->fonts.rus_10x10)
1659          {
1660             rgui_fonts_free(rgui);
1661             *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE) = RETRO_LANGUAGE_ENGLISH;
1662             runloop_msg_queue_push(
1663                   msg_hash_to_str(MSG_RGUI_MISSING_FONTS), 1, 256, false, NULL,
1664                   MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1665             goto english;
1666          }
1667 
1668          rgui->font_width         = FONT_10X10_WIDTH;
1669          rgui->font_height        = FONT_10X10_HEIGHT;
1670          rgui->font_width_stride  = FONT_10X10_WIDTH_STRIDE;
1671          rgui->font_height_stride = FONT_10X10_HEIGHT_STRIDE;
1672          rgui->language           = language;
1673          break;
1674       case RETRO_LANGUAGE_ARABIC:
1675       case RETRO_LANGUAGE_GREEK:
1676       case RETRO_LANGUAGE_PERSIAN:
1677       case RETRO_LANGUAGE_HEBREW:
1678       default:
1679          /* We do not have fonts for these
1680           * languages - fallback to English */
1681          *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE) = RETRO_LANGUAGE_ENGLISH;
1682          runloop_msg_queue_push(
1683                msg_hash_to_str(MSG_RGUI_INVALID_LANGUAGE), 1, 256, false, NULL,
1684                MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1685          goto english;
1686    }
1687 
1688    return true;
1689 
1690 english:
1691 #endif
1692    rgui->fonts.regular      = bitmapfont_get_lut();
1693 
1694    if (!rgui->fonts.regular)
1695       return false;
1696 
1697    if (rgui->fonts.regular->glyph_max <
1698          (RGUI_NUM_FONT_GLYPHS_EXTENDED - 1))
1699    {
1700       rgui_fonts_free(rgui);
1701       return false;
1702    }
1703 
1704    rgui->font_width         = FONT_WIDTH;
1705    rgui->font_height        = FONT_HEIGHT;
1706    rgui->font_width_stride  = FONT_WIDTH_STRIDE;
1707    rgui->font_height_stride = FONT_HEIGHT_STRIDE;
1708 
1709    rgui->language           = RETRO_LANGUAGE_ENGLISH;
1710 
1711    return true;
1712 }
1713 
rgui_fill_rect(uint16_t * data,unsigned fb_width,unsigned fb_height,unsigned x,unsigned y,unsigned width,unsigned height,uint16_t dark_color,uint16_t light_color,bool thickness)1714 static void rgui_fill_rect(
1715       uint16_t *data,
1716       unsigned fb_width, unsigned fb_height,
1717       unsigned x, unsigned y,
1718       unsigned width, unsigned height,
1719       uint16_t dark_color, uint16_t light_color,
1720       bool thickness)
1721 {
1722    unsigned x_index, y_index;
1723    unsigned x_start = x <= fb_width  ? x : fb_width;
1724    unsigned y_start = y <= fb_height ? y : fb_height;
1725    unsigned x_end   = x + width;
1726    unsigned y_end   = y + height;
1727    size_t x_size;
1728    uint16_t scanline_even[RGUI_MAX_FB_WIDTH]; /* Initial values don't matter here */
1729    uint16_t scanline_odd[RGUI_MAX_FB_WIDTH];
1730 
1731    /* Note: unlike rgui_color_rect() and rgui_draw_particle(),
1732     * this function is frequently used to fill large areas.
1733     * We therefore gain significant performance benefits
1734     * from using memcpy() tricks... */
1735 
1736    x_end  = x_end <= fb_width  ? x_end : fb_width;
1737    y_end  = y_end <= fb_height ? y_end : fb_height;
1738    x_size = (x_end - x_start) * sizeof(uint16_t);
1739 
1740    /* Sanity check */
1741    if (x_size == 0)
1742       return;
1743 
1744    /* If dark_color and light_color are the same,
1745     * perform a solid fill */
1746    if (dark_color == light_color)
1747    {
1748       uint16_t *src = scanline_even + x_start;
1749       uint16_t *dst = data + x_start;
1750 
1751       /* Populate source array */
1752       for (x_index = x_start; x_index < x_end; x_index++)
1753          *(scanline_even + x_index) = dark_color;
1754 
1755       /* Fill destination array */
1756       for (y_index = y_start; y_index < y_end; y_index++)
1757          memcpy(dst + (y_index * fb_width), src, x_size);
1758    }
1759    else if (thickness)
1760    {
1761       uint16_t *src_a      = NULL;
1762       uint16_t *src_b      = NULL;
1763       uint16_t *src_c      = NULL;
1764       uint16_t *src_d      = NULL;
1765       uint16_t *dst        = data + x_start;
1766 
1767       /* Determine in which order the source arrays
1768        * should be copied */
1769       switch (y_start & 0x3)
1770       {
1771          case 0x1:
1772             src_a = scanline_even + x_start;
1773             src_b = scanline_odd  + x_start;
1774             src_c = src_b;
1775             src_d = src_a;
1776             break;
1777          case 0x2:
1778             src_a = scanline_odd  + x_start;
1779             src_b = src_a;
1780             src_c = scanline_even + x_start;
1781             src_d = src_c;
1782             break;
1783          case 0x3:
1784             src_a = scanline_odd  + x_start;
1785             src_b = scanline_even + x_start;
1786             src_c = src_b;
1787             src_d = src_a;
1788             break;
1789          case 0x0:
1790          default:
1791             src_a = scanline_even + x_start;
1792             src_b = src_a;
1793             src_c = scanline_odd  + x_start;
1794             src_d = src_c;
1795             break;
1796       }
1797 
1798       /* Populate source arrays */
1799       for (x_index = x_start; x_index < x_end; x_index++)
1800       {
1801          bool x_is_even = (((x_index >> 1) & 1) == 0);
1802          *(scanline_even + x_index) = x_is_even ? dark_color  : light_color;
1803          *(scanline_odd  + x_index) = x_is_even ? light_color : dark_color;
1804       }
1805 
1806       /* Fill destination array */
1807       for (y_index = y_start    ; y_index < y_end; y_index += 4)
1808          memcpy(dst + (y_index * fb_width), src_a, x_size);
1809 
1810       for (y_index = y_start + 1; y_index < y_end; y_index += 4)
1811          memcpy(dst + (y_index * fb_width), src_b, x_size);
1812 
1813       for (y_index = y_start + 2; y_index < y_end; y_index += 4)
1814          memcpy(dst + (y_index * fb_width), src_c, x_size);
1815 
1816       for (y_index = y_start + 3; y_index < y_end; y_index += 4)
1817          memcpy(dst + (y_index * fb_width), src_d, x_size);
1818    }
1819    else
1820    {
1821       uint16_t *src_a      = NULL;
1822       uint16_t *src_b      = NULL;
1823       uint16_t *dst        = data + x_start;
1824 
1825       /* Determine in which order the source arrays
1826        * should be copied */
1827       if ((y_start & 1) == 0)
1828       {
1829          src_a = scanline_even + x_start;
1830          src_b = scanline_odd  + x_start;
1831       }
1832       else
1833       {
1834          src_a = scanline_odd  + x_start;
1835          src_b = scanline_even + x_start;
1836       }
1837 
1838       /* Populate source arrays */
1839       for (x_index = x_start; x_index < x_end; x_index++)
1840       {
1841          bool x_is_even = ((x_index & 1) == 0);
1842          *(scanline_even + x_index) = x_is_even ? dark_color  : light_color;
1843          *(scanline_odd  + x_index) = x_is_even ? light_color : dark_color;
1844       }
1845 
1846       /* Fill destination array */
1847       for (y_index = y_start    ; y_index < y_end; y_index += 2)
1848          memcpy(dst + (y_index * fb_width), src_a, x_size);
1849 
1850       for (y_index = y_start + 1; y_index < y_end; y_index += 2)
1851          memcpy(dst + (y_index * fb_width), src_b, x_size);
1852    }
1853 }
1854 
rgui_color_rect(uint16_t * data,unsigned fb_width,unsigned fb_height,unsigned x,unsigned y,unsigned width,unsigned height,uint16_t color)1855 static void rgui_color_rect(
1856       uint16_t *data,
1857       unsigned fb_width, unsigned fb_height,
1858       unsigned x, unsigned y,
1859       unsigned width, unsigned height,
1860       uint16_t color)
1861 {
1862    unsigned x_index, y_index;
1863    unsigned x_start = x <= fb_width  ? x : fb_width;
1864    unsigned y_start = y <= fb_height ? y : fb_height;
1865    unsigned x_end   = x + width;
1866    unsigned y_end   = y + height;
1867 
1868    x_end = x_end <= fb_width  ? x_end : fb_width;
1869    y_end = y_end <= fb_height ? y_end : fb_height;
1870 
1871    for (y_index = y_start; y_index < y_end; y_index++)
1872    {
1873       uint16_t *data_ptr = data + (y_index * fb_width);
1874       for (x_index = x_start; x_index < x_end; x_index++)
1875          *(data_ptr + x_index) = color;
1876    }
1877 }
1878 
rgui_render_border(rgui_t * rgui,uint16_t * data,unsigned fb_width,unsigned fb_height)1879 static void rgui_render_border(rgui_t *rgui, uint16_t *data,
1880       unsigned fb_width, unsigned fb_height)
1881 {
1882    uint16_t dark_color;
1883    uint16_t light_color;
1884    bool thickness;
1885 
1886    /* Sanity check */
1887    if (!rgui || !data)
1888       return;
1889 
1890    dark_color   = rgui->colors.border_dark_color;
1891    light_color  = rgui->colors.border_light_color;
1892    thickness    = rgui->border_thickness;
1893 
1894    /* Draw border */
1895    rgui_fill_rect(data, fb_width, fb_height,
1896          5, 5, fb_width - 10, 5,
1897          dark_color, light_color, thickness);
1898    rgui_fill_rect(data, fb_width, fb_height,
1899          5, fb_height - 10, fb_width - 10, 5,
1900          dark_color, light_color, thickness);
1901    rgui_fill_rect(data, fb_width, fb_height,
1902          5, 5, 5, fb_height - 10,
1903          dark_color, light_color, thickness);
1904    rgui_fill_rect(data, fb_width, fb_height,
1905          fb_width - 10, 5, 5, fb_height - 10,
1906          dark_color, light_color, thickness);
1907 
1908    /* Draw drop shadow, if required */
1909    if (rgui->shadow_enable)
1910    {
1911       uint16_t shadow_color = rgui->colors.shadow_color;
1912 
1913       rgui_color_rect(data, fb_width, fb_height,
1914             10, 10, 1, fb_height - 20, shadow_color);
1915       rgui_color_rect(data, fb_width, fb_height,
1916             10, 10, fb_width - 20, 1, shadow_color);
1917       rgui_color_rect(data, fb_width, fb_height,
1918             fb_width - 5, 6, 1, fb_height - 10, shadow_color);
1919       rgui_color_rect(data, fb_width, fb_height,
1920             6, fb_height - 5, fb_width - 10, 1, shadow_color);
1921    }
1922 }
1923 
1924 /* Returns true if particle is on screen */
rgui_draw_particle(uint16_t * data,unsigned fb_width,unsigned fb_height,int x,int y,unsigned width,unsigned height,uint16_t color)1925 static bool INLINE rgui_draw_particle(
1926       uint16_t *data,
1927       unsigned fb_width, unsigned fb_height,
1928       int x, int y,
1929       unsigned width, unsigned height,
1930       uint16_t color)
1931 {
1932    unsigned x_index, y_index;
1933 
1934    /* This great convoluted mess just saves us
1935     * having to perform comparisons on every
1936     * iteration of the for loops... */
1937    int x_start = x > 0 ? x : 0;
1938    int y_start = y > 0 ? y : 0;
1939    int x_end = x + width;
1940    int y_end = y + height;
1941 
1942    x_start = x_start <= (int)fb_width  ? x_start : fb_width;
1943    y_start = y_start <= (int)fb_height ? y_start : fb_height;
1944 
1945    x_end = x_end >  0        ? x_end : 0;
1946    x_end = x_end <= (int)fb_width ? x_end : fb_width;
1947 
1948    y_end = y_end >  0         ? y_end : 0;
1949    y_end = y_end <= (int)fb_height ? y_end : fb_height;
1950 
1951    for (y_index = (unsigned)y_start; y_index < (unsigned)y_end; y_index++)
1952    {
1953       uint16_t *data_ptr = data + (y_index * fb_width);
1954       for (x_index = (unsigned)x_start; x_index < (unsigned)x_end; x_index++)
1955          *(data_ptr + x_index) = color;
1956    }
1957 
1958    return (x_end > x_start) && (y_end > y_start);
1959 }
1960 
rgui_init_particle_effect(rgui_t * rgui,gfx_display_t * p_disp)1961 static void rgui_init_particle_effect(rgui_t *rgui,
1962       gfx_display_t *p_disp)
1963 {
1964    size_t i;
1965    unsigned fb_width  = p_disp->framebuf_width;
1966    unsigned fb_height = p_disp->framebuf_height;
1967 
1968    switch (rgui->particle_effect)
1969    {
1970       case RGUI_PARTICLE_EFFECT_SNOW:
1971       case RGUI_PARTICLE_EFFECT_SNOW_ALT:
1972          {
1973             for (i = 0; i < RGUI_NUM_PARTICLES; i++)
1974             {
1975                rgui_particle_t *particle = &rgui->particles[i];
1976 
1977                particle->a = (float)(rand() % fb_width);
1978                particle->b = (float)(rand() % fb_height);
1979                particle->c = (float)(rand() % 64 - 16) * 0.1f;
1980                particle->d = (float)(rand() % 64 - 48) * 0.1f;
1981             }
1982          }
1983          break;
1984       case RGUI_PARTICLE_EFFECT_RAIN:
1985          {
1986             uint8_t weights[] = { /* 60 entries */
1987                2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
1988                3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
1989                4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
1990                5, 5, 5, 5, 5, 5, 5, 5,
1991                6, 6, 6, 6, 6, 6,
1992                7, 7, 7, 7,
1993                8, 8, 8,
1994                9, 9,
1995                10};
1996             unsigned num_drops = (unsigned)(0.85f * ((float)fb_width / (float)RGUI_MAX_FB_WIDTH) * (float)RGUI_NUM_PARTICLES);
1997 
1998             num_drops = num_drops < RGUI_NUM_PARTICLES ? num_drops : RGUI_NUM_PARTICLES;
1999 
2000             for (i = 0; i < num_drops; i++)
2001             {
2002                rgui_particle_t *particle = &rgui->particles[i];
2003 
2004                /* x pos */
2005                particle->a = (float)(rand() % (fb_width / 3)) * 3.0f;
2006                /* y pos */
2007                particle->b = (float)(rand() % fb_height);
2008                /* drop length */
2009                particle->c = (float)weights[(unsigned)(rand() % 60)];
2010                /* drop speed (larger drops fall faster) */
2011                particle->d = (particle->c / 12.0f) * (0.5f + ((float)(rand() % 150) / 200.0f));
2012             }
2013          }
2014          break;
2015       case RGUI_PARTICLE_EFFECT_VORTEX:
2016          {
2017             float max_radius         = (float)sqrt((double)((fb_width * fb_width) + (fb_height * fb_height))) / 2.0f;
2018             float one_degree_radians = PI / 360.0f;
2019 
2020             for (i = 0; i < RGUI_NUM_PARTICLES; i++)
2021             {
2022                rgui_particle_t *particle = &rgui->particles[i];
2023 
2024                /* radius */
2025                particle->a = 1.0f + (((float)rand() / (float)RAND_MAX) * max_radius);
2026                /* theta */
2027                particle->b = ((float)rand() / (float)RAND_MAX) * 2.0f * PI;
2028                /* radial speed */
2029                particle->c = (float)((rand() % 100) + 1) * 0.001f;
2030                /* rotational speed */
2031                particle->d = (((float)((rand() % 50) + 1) / 200.0f) + 0.1f) * one_degree_radians;
2032             }
2033          }
2034          break;
2035       case RGUI_PARTICLE_EFFECT_STARFIELD:
2036          {
2037             for (i = 0; i < RGUI_NUM_PARTICLES; i++)
2038             {
2039                rgui_particle_t *particle = &rgui->particles[i];
2040 
2041                /* x pos */
2042                particle->a = (float)(rand() % fb_width);
2043                /* y pos */
2044                particle->b = (float)(rand() % fb_height);
2045                /* depth */
2046                particle->c = (float)fb_width;
2047                /* speed */
2048                particle->d = 1.0f + ((float)(rand() % 20) * 0.01f);
2049             }
2050          }
2051          break;
2052       default:
2053          /* Do nothing... */
2054          break;
2055    }
2056 }
2057 
rgui_render_particle_effect(rgui_t * rgui,gfx_animation_t * p_anim,unsigned fb_width,unsigned fb_height)2058 static void rgui_render_particle_effect(
2059       rgui_t *rgui,
2060       gfx_animation_t *p_anim,
2061       unsigned fb_width,
2062       unsigned fb_height)
2063 {
2064    size_t i;
2065    uint16_t particle_color;
2066    /* Give speed factor a long, awkward name to minimise
2067     * risk of clashing with specific particle effect
2068     * implementation variables... */
2069    float global_speed_factor        = 1.0f;
2070    settings_t        *settings      = config_get_ptr();
2071    float particle_effect_speed      = 0.0f;
2072    bool particle_effect_screensaver = false;
2073    uint16_t *frame_buf_data         = NULL;
2074 
2075    if (settings)
2076    {
2077       particle_effect_speed         = settings->floats.menu_rgui_particle_effect_speed;
2078       particle_effect_screensaver   = settings->bools.menu_rgui_particle_effect_screensaver;
2079    }
2080 
2081    /* Sanity check */
2082    if (!rgui || !rgui->frame_buf.data)
2083       return;
2084 
2085    frame_buf_data = rgui->frame_buf.data;
2086 
2087    /* Check whether screensaver is currently active */
2088    if (rgui->show_screensaver)
2089    {
2090       /* Return early if screensaver animation is
2091        * disabled */
2092       if (!particle_effect_screensaver)
2093          return;
2094       particle_color = rgui->colors.ss_particle_color;
2095    }
2096    else
2097       particle_color = rgui->colors.particle_color;
2098 
2099    /* Adjust global animation speed */
2100    /* > Apply user configured speed multiplier */
2101    if (particle_effect_speed > 0.0001f)
2102       global_speed_factor = particle_effect_speed;
2103 
2104    /* > Account for non-standard frame times
2105     *   (high/low refresh rates, or frame drops) */
2106    global_speed_factor   *= p_anim->delta_time
2107       / particle_effect_period;
2108 
2109    /* Note: It would be more elegant to have 'update' and 'draw'
2110     * as separate functions, since 'update' is the part that
2111     * varies with particle effect whereas 'draw' is always
2112     * pretty much the same. However, this has the following
2113     * disadvantages:
2114     * - It means we have to loop through all particles twice,
2115     *   and given that we're already using a heap of CPU cycles
2116     *   to draw these effects any further performance overheads
2117     *   are to be avoided
2118     * - It locks us into a particular draw style. e.g. What if
2119     *   an effect calls for round particles, instead of square
2120     *   ones? This would make a mess of any 'standardised'
2121     *   drawing
2122     * So we go with the simple option of having the entire
2123     * update/draw sequence here. This results in some code
2124     * repetition, but it has better performance and allows for
2125     * complete flexibility */
2126 
2127    switch (rgui->particle_effect)
2128    {
2129       case RGUI_PARTICLE_EFFECT_SNOW:
2130       case RGUI_PARTICLE_EFFECT_SNOW_ALT:
2131          {
2132             unsigned particle_size;
2133             bool on_screen;
2134 
2135             for (i = 0; i < RGUI_NUM_PARTICLES; i++)
2136             {
2137                rgui_particle_t *particle = &rgui->particles[i];
2138 
2139                /* Update particle 'speed' */
2140                particle->c = particle->c + (float)(rand() % 16 - 9) * 0.01f;
2141                particle->d = particle->d + (float)(rand() % 16 - 7) * 0.01f;
2142 
2143                particle->c = (particle->c < -0.4f) ? -0.4f : particle->c;
2144                particle->c = (particle->c >  0.1f) ?  0.1f : particle->c;
2145 
2146                particle->d = (particle->d < -0.1f) ? -0.1f : particle->d;
2147                particle->d = (particle->d >  0.4f) ?  0.4f : particle->d;
2148 
2149                /* Update particle location */
2150                particle->a = fmod(particle->a + (global_speed_factor * particle->c), fb_width);
2151                particle->b = fmod(particle->b + (global_speed_factor * particle->d), fb_height);
2152 
2153                /* Get particle size */
2154                particle_size = 1;
2155                if (rgui->particle_effect == RGUI_PARTICLE_EFFECT_SNOW_ALT)
2156                {
2157                   /* Gives the following distribution:
2158                    * 1x1: 96
2159                    * 2x2: 128
2160                    * 3x3: 32 */
2161                   if (!(i & 0x2))
2162                      particle_size = 2;
2163                   else if ((i & 0x7) == 0x7)
2164                      particle_size = 3;
2165                }
2166 
2167                /* Draw particle */
2168                on_screen = rgui_draw_particle(frame_buf_data, fb_width, fb_height,
2169                                  (int)particle->a, (int)particle->b,
2170                                  particle_size, particle_size, particle_color);
2171 
2172                /* Reset particle if it has fallen off screen */
2173                if (!on_screen)
2174                {
2175                   particle->a = (particle->a < 0.0f) ? (particle->a + (float)fb_width)  : particle->a;
2176                   particle->b = (particle->b < 0.0f) ? (particle->b + (float)fb_height) : particle->b;
2177                }
2178             }
2179          }
2180          break;
2181       case RGUI_PARTICLE_EFFECT_RAIN:
2182          {
2183             uint8_t weights[] = { /* 60 entries */
2184                2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2185                3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
2186                4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
2187                5, 5, 5, 5, 5, 5, 5, 5,
2188                6, 6, 6, 6, 6, 6,
2189                7, 7, 7, 7,
2190                8, 8, 8,
2191                9, 9,
2192                10};
2193             unsigned num_drops = (unsigned)(0.85f * ((float)fb_width / (float)RGUI_MAX_FB_WIDTH) * (float)RGUI_NUM_PARTICLES);
2194             bool on_screen;
2195 
2196             num_drops = num_drops < RGUI_NUM_PARTICLES ? num_drops : RGUI_NUM_PARTICLES;
2197 
2198             for (i = 0; i < num_drops; i++)
2199             {
2200                rgui_particle_t *particle = &rgui->particles[i];
2201 
2202                /* Draw particle */
2203                on_screen = rgui_draw_particle(frame_buf_data, fb_width, fb_height,
2204                                  (int)particle->a, (int)particle->b,
2205                                  2, (unsigned)particle->c, particle_color);
2206 
2207                /* Update y pos */
2208                particle->b += particle->d * global_speed_factor;
2209 
2210                /* Reset particle if it has fallen off the bottom of the screen */
2211                if (!on_screen)
2212                {
2213                   /* x pos */
2214                   particle->a = (float)(rand() % (fb_width / 3)) * 3.0f;
2215                   /* y pos */
2216                   particle->b = 0.0f;
2217                   /* drop length */
2218                   particle->c = (float)weights[(unsigned)(rand() % 60)];
2219                   /* drop speed (larger drops fall faster) */
2220                   particle->d = (particle->c / 12.0f) * (0.5f + ((float)(rand() % 150) / 200.0f));
2221                }
2222             }
2223          }
2224          break;
2225       case RGUI_PARTICLE_EFFECT_VORTEX:
2226          {
2227             float max_radius         = (float)sqrt((double)((fb_width * fb_width) + (fb_height * fb_height))) / 2.0f;
2228             float one_degree_radians = PI / 360.0f;
2229             int x_centre             = (int)(fb_width >> 1);
2230             int y_centre             = (int)(fb_height >> 1);
2231             unsigned particle_size;
2232             float r_speed, theta_speed;
2233             int x, y;
2234 
2235             for (i = 0; i < RGUI_NUM_PARTICLES; i++)
2236             {
2237                rgui_particle_t *particle = &rgui->particles[i];
2238 
2239                /* Get particle location */
2240                x = (int)(particle->a * cos(particle->b)) + x_centre;
2241                y = (int)(particle->a * sin(particle->b)) + y_centre;
2242 
2243                /* Get particle size */
2244                particle_size = 1 + (unsigned)(((1.0f - ((max_radius - particle->a) / max_radius)) * 3.5f) + 0.5f);
2245 
2246                /* Draw particle */
2247                rgui_draw_particle(frame_buf_data, fb_width, fb_height,
2248                      x, y, particle_size, particle_size, particle_color);
2249 
2250                /* Update particle speed */
2251                r_speed     = particle->c * global_speed_factor;
2252                theta_speed = particle->d * global_speed_factor;
2253                if ((particle->a > 0.0f) && (particle->a < (float)fb_height))
2254                {
2255                   float base_scale_factor = ((float)fb_height - particle->a) / (float)fb_height;
2256                   r_speed     *= 1.0f + (base_scale_factor * 8.0f);
2257                   theta_speed *= 1.0f + (base_scale_factor * base_scale_factor * 6.0f);
2258                }
2259                particle->a -= r_speed;
2260                particle->b += theta_speed;
2261 
2262                /* Reset particle if it has reached the centre of the screen */
2263                if (particle->a < 0.0f)
2264                {
2265                   /* radius
2266                    * Note: In theory, this should be:
2267                    * > particle->a = max_radius;
2268                    * ...but it turns out that spawning new particles at random
2269                    * locations produces a more visually appealing result... */
2270                   particle->a = 1.0f + (((float)rand() / (float)RAND_MAX) * max_radius);
2271                   /* theta */
2272                   particle->b = ((float)rand() / (float)RAND_MAX) * 2.0f * PI;
2273                   /* radial speed */
2274                   particle->c = (float)((rand() % 100) + 1) * 0.001f;
2275                   /* rotational speed */
2276                   particle->d = (((float)((rand() % 50) + 1) / 200.0f) + 0.1f) * one_degree_radians;
2277                }
2278             }
2279          }
2280          break;
2281       case RGUI_PARTICLE_EFFECT_STARFIELD:
2282          {
2283             float focal_length = (float)fb_width * 2.0f;
2284             int x_centre       = (int)(fb_width >> 1);
2285             int y_centre       = (int)(fb_height >> 1);
2286             unsigned particle_size;
2287             int x, y;
2288             bool on_screen;
2289 
2290             /* Based on an example found here:
2291              * https://codepen.io/nodws/pen/pejBNb */
2292             for (i = 0; i < RGUI_NUM_PARTICLES; i++)
2293             {
2294                rgui_particle_t *particle = &rgui->particles[i];
2295 
2296                /* Get particle location */
2297                x = (int)((particle->a - (float)x_centre) * (focal_length / particle->c));
2298                x += x_centre;
2299 
2300                y = (int)((particle->b - (float)y_centre) * (focal_length / particle->c));
2301                y += y_centre;
2302 
2303                /* Get particle size */
2304                particle_size = (unsigned)(focal_length / (2.0f * particle->c));
2305 
2306                /* Draw particle */
2307                on_screen = rgui_draw_particle(frame_buf_data, fb_width, fb_height,
2308                                  x, y, particle_size, particle_size, particle_color);
2309 
2310                /* Update depth */
2311                particle->c -= particle->d * global_speed_factor;
2312 
2313                /* Reset particle if it has:
2314                 * - Dropped off the edge of the screen
2315                 * - Reached the screen depth
2316                 * - Grown larger than 16 pixels across
2317                 *   (this is an arbitrary limit, set to reduce overall
2318                 *   performance impact - i.e. larger particles are slower
2319                 *   to draw, and without setting a limit they can fill the screen...) */
2320                if (!on_screen || (particle->c <= 0.0f) || particle_size > 16)
2321                {
2322                   /* x pos */
2323                   particle->a = (float)(rand() % fb_width);
2324                   /* y pos */
2325                   particle->b = (float)(rand() % fb_height);
2326                   /* depth */
2327                   particle->c = (float)fb_width;
2328                   /* speed */
2329                   particle->d = 1.0f + ((float)(rand() % 20) * 0.01f);
2330                }
2331             }
2332          }
2333          break;
2334       default:
2335          /* Do nothing... */
2336          break;
2337    }
2338 
2339    /* If border is enabled, it must be drawn *above*
2340     * particle effect
2341     * (Wastes CPU cycles, but nothing we can do about it...) */
2342    if (rgui->border_enable &&
2343        !rgui->show_wallpaper &&
2344        !rgui->show_screensaver)
2345       rgui_render_border(rgui, frame_buf_data, fb_width, fb_height);
2346 }
2347 
process_wallpaper(rgui_t * rgui,struct texture_image * image)2348 static void process_wallpaper(rgui_t *rgui, struct texture_image *image)
2349 {
2350    unsigned x, y;
2351    unsigned x_crop_offset;
2352    unsigned y_crop_offset;
2353    frame_buf_t *background_buf = &rgui->background_buf;
2354 
2355    /* Sanity check */
2356    if (!image->pixels ||
2357        (image->width  < background_buf->width)  ||
2358        (image->height < background_buf->height) ||
2359        !background_buf->data)
2360       return;
2361 
2362    /* In most cases, image size will be identical
2363     * to wallpaper buffer size - but wallpaper buffer
2364     * will be smaller than expected if:
2365     * - This is a GEKKO platform (these only support
2366     *   a 16:9 framebuffer width of 424 instead of
2367     *   the usual 426...)
2368     * - The current display resolution is less than
2369     *   240p - in which case, the framebuffer will
2370     *   scale down to a minimum of 192p
2371     * If the wallpaper buffer is undersized, we have
2372     * to crop the source image */
2373    x_crop_offset = (image->width  - background_buf->width)  >> 1;
2374    y_crop_offset = (image->height - background_buf->height) >> 1;
2375 
2376    /* Copy image to wallpaper buffer, performing pixel format conversion */
2377    for (x = 0; x < background_buf->width; x++)
2378    {
2379       for (y = 0; y < background_buf->height; y++)
2380       {
2381          background_buf->data[x + (y * background_buf->width)] =
2382                argb32_to_pixel_platform_format(image->pixels[
2383                      (x + x_crop_offset) +
2384                      ((y + y_crop_offset) * image->width)]);
2385       }
2386    }
2387 
2388    rgui->show_wallpaper = true;
2389 
2390    /* Tell menu that a display update is required */
2391    rgui->force_redraw = true;
2392 }
2393 
request_thumbnail(thumbnail_t * thumbnail,enum gfx_thumbnail_id thumbnail_id,uint32_t * queue_size,const char * path,bool * file_missing)2394 static bool request_thumbnail(
2395       thumbnail_t *thumbnail,
2396       enum gfx_thumbnail_id thumbnail_id,
2397       uint32_t *queue_size,
2398       const char *path,
2399       bool *file_missing)
2400 {
2401    /* Do nothing if current thumbnail path hasn't changed */
2402    if (!string_is_empty(path) && !string_is_empty(thumbnail->path))
2403       if (string_is_equal(thumbnail->path, path))
2404          return true;
2405 
2406    /* 'Reset' current thumbnail */
2407    thumbnail->width = 0;
2408    thumbnail->height = 0;
2409    thumbnail->is_valid = false;
2410    thumbnail->path[0] = '\0';
2411 
2412    /* Ensure that new path is valid... */
2413    if (!string_is_empty(path))
2414    {
2415       strlcpy(thumbnail->path, path, sizeof(thumbnail->path));
2416       if (path_is_valid(path))
2417       {
2418          /* Would like to cancel any existing image load tasks
2419           * here, but can't see how to do it... */
2420          if (task_push_image_load(thumbnail->path,
2421                   video_driver_supports_rgba(), 0,
2422                   (thumbnail_id == GFX_THUMBNAIL_LEFT) ?
2423             menu_display_handle_left_thumbnail_upload
2424             : menu_display_handle_thumbnail_upload, NULL))
2425          {
2426             *queue_size = *queue_size + 1;
2427             return true;
2428          }
2429       }
2430       else
2431          *file_missing = true;
2432    }
2433 
2434    return false;
2435 }
2436 
downscale_thumbnail(rgui_t * rgui,unsigned max_width,unsigned max_height,struct texture_image * image_src,struct texture_image * image_dst)2437 static bool downscale_thumbnail(rgui_t *rgui,
2438       unsigned max_width, unsigned max_height,
2439       struct texture_image *image_src, struct texture_image *image_dst)
2440 {
2441    /* Determine output dimensions */
2442    float display_aspect_ratio    = (float)max_width / (float)max_height;
2443    float         aspect_ratio    = (float)image_src->width
2444       / (float)image_src->height;
2445    settings_t       *settings    = config_get_ptr();
2446    unsigned thumbnail_downscaler = settings->uints.menu_rgui_thumbnail_downscaler;
2447 
2448    if (aspect_ratio > display_aspect_ratio)
2449    {
2450       image_dst->width = max_width;
2451       image_dst->height = image_src->height * max_width / image_src->width;
2452       /* Account for any possible rounding errors... */
2453       image_dst->height = (image_dst->height < 1) ? 1 : image_dst->height;
2454       image_dst->height = (image_dst->height > max_height) ? max_height : image_dst->height;
2455    }
2456    else
2457    {
2458       image_dst->height = max_height;
2459       image_dst->width = image_src->width * max_height / image_src->height;
2460       /* Account for any possible rounding errors... */
2461       image_dst->width = (image_dst->width < 1) ? 1 : image_dst->width;
2462       image_dst->width = (image_dst->width > max_width) ? max_width : image_dst->width;
2463    }
2464 
2465    /* Allocate pixel buffer */
2466    image_dst->pixels = (uint32_t*)calloc(image_dst->width * image_dst->height, sizeof(uint32_t));
2467    if (!image_dst->pixels)
2468       return false;
2469 
2470    /* Determine scaling method */
2471    if (thumbnail_downscaler == RGUI_THUMB_SCALE_POINT)
2472    {
2473       uint32_t x_ratio, y_ratio;
2474       unsigned x_src, y_src;
2475       unsigned x_dst, y_dst;
2476 
2477       /* Perform nearest neighbour resampling
2478        * > Fastest method, minimal performance impact */
2479       x_ratio = ((image_src->width  << 16) / image_dst->width);
2480       y_ratio = ((image_src->height << 16) / image_dst->height);
2481 
2482       for (y_dst = 0; y_dst < image_dst->height; y_dst++)
2483       {
2484          y_src = (y_dst * y_ratio) >> 16;
2485          for (x_dst = 0; x_dst < image_dst->width; x_dst++)
2486          {
2487             x_src = (x_dst * x_ratio) >> 16;
2488             image_dst->pixels[(y_dst * image_dst->width) + x_dst] = image_src->pixels[(y_src * image_src->width) + x_src];
2489          }
2490       }
2491    }
2492    else
2493    {
2494       /* Perform either bilinear or sinc (Lanczos3) resampling
2495        * using libretro-common scaler
2496        * > Better quality, but substantially higher performance
2497        *   impact - although not an issue on desktop-class
2498        *   hardware */
2499       rgui->image_scaler.in_width    = image_src->width;
2500       rgui->image_scaler.in_height   = image_src->height;
2501       rgui->image_scaler.in_stride   = image_src->width * sizeof(uint32_t);
2502       rgui->image_scaler.in_fmt      = SCALER_FMT_ARGB8888;
2503 
2504       rgui->image_scaler.out_width   = image_dst->width;
2505       rgui->image_scaler.out_height  = image_dst->height;
2506       rgui->image_scaler.out_stride  = image_dst->width * sizeof(uint32_t);
2507       rgui->image_scaler.out_fmt     = SCALER_FMT_ARGB8888;
2508 
2509       rgui->image_scaler.scaler_type = (thumbnail_downscaler == RGUI_THUMB_SCALE_SINC) ?
2510          SCALER_TYPE_SINC : SCALER_TYPE_BILINEAR;
2511 
2512       /* This reset is redundant, since scaler_ctx_gen_filter()
2513        * calls it - but do it anyway in case the
2514        * scaler_ctx_gen_filter() internals ever change... */
2515       scaler_ctx_gen_reset(&rgui->image_scaler);
2516       if (!scaler_ctx_gen_filter(&rgui->image_scaler))
2517       {
2518          /* Could be leftovers if scaler_ctx_gen_filter()
2519           * fails, so reset just in case... */
2520          scaler_ctx_gen_reset(&rgui->image_scaler);
2521          return false;
2522       }
2523 
2524       scaler_ctx_scale(&rgui->image_scaler, image_dst->pixels, image_src->pixels);
2525       /* Reset again - don't want to leave anything hanging around
2526        * if the user switches back to nearest neighbour scaling */
2527       scaler_ctx_gen_reset(&rgui->image_scaler);
2528    }
2529 
2530    return true;
2531 }
2532 
process_thumbnail(rgui_t * rgui,thumbnail_t * thumbnail,uint32_t * queue_size,struct texture_image * image_src)2533 static void process_thumbnail(rgui_t *rgui, thumbnail_t *thumbnail, uint32_t *queue_size, struct texture_image *image_src)
2534 {
2535    unsigned x, y;
2536    struct texture_image *image          = NULL;
2537    struct texture_image image_resampled = {
2538       NULL,
2539       0,
2540       0,
2541       false
2542    };
2543 
2544    /* Ensure that we only process the most recently loaded
2545     * thumbnail image (i.e. don't waste CPU cycles processing
2546     * old images if we have a backlog) */
2547    if (*queue_size > 0)
2548       *queue_size = *queue_size - 1;
2549    if (*queue_size > 0)
2550       return;
2551 
2552    /* Sanity check */
2553    if (!image_src->pixels || (image_src->width < 1) || (image_src->height < 1) || !thumbnail->data)
2554       return;
2555 
2556    /* Downscale thumbnail if it exceeds maximum size limits */
2557    if ((image_src->width > thumbnail->max_width) || (image_src->height > thumbnail->max_height))
2558    {
2559       if (!downscale_thumbnail(rgui, thumbnail->max_width, thumbnail->max_height, image_src, &image_resampled))
2560       {
2561          if (image_resampled.pixels)
2562             free(image_resampled.pixels);
2563          return;
2564       }
2565       image = &image_resampled;
2566    }
2567    else
2568    {
2569       image = image_src;
2570    }
2571 
2572    thumbnail->width = image->width;
2573    thumbnail->height = image->height;
2574 
2575    /* Copy image to thumbnail buffer, performing pixel format conversion */
2576    for (x = 0; x < thumbnail->width; x++)
2577    {
2578       for (y = 0; y < thumbnail->height; y++)
2579       {
2580          thumbnail->data[x + (y * thumbnail->width)] =
2581             argb32_to_pixel_platform_format(image->pixels[x + (y * thumbnail->width)]);
2582       }
2583    }
2584 
2585    thumbnail->is_valid = true;
2586 
2587    /* Tell menu that a display update is required */
2588    rgui->force_redraw = true;
2589 
2590    /* Clean up */
2591    image = NULL;
2592    if (image_resampled.pixels)
2593       free(image_resampled.pixels);
2594    image_resampled.pixels = NULL;
2595 }
2596 
rgui_load_image(void * userdata,void * data,enum menu_image_type type)2597 static bool rgui_load_image(void *userdata, void *data, enum menu_image_type type)
2598 {
2599    rgui_t *rgui         = (rgui_t*)userdata;
2600    settings_t *settings = config_get_ptr();
2601 
2602    if (!rgui || !settings)
2603       return false;
2604 
2605    if (!data)
2606    {
2607       /* This means we have a 'broken' image. There is no
2608        * data, but we still have to decrement any thumbnail
2609        * queues (otherwise further thumbnail processing will
2610        * be blocked) */
2611       switch (type)
2612       {
2613          case MENU_IMAGE_THUMBNAIL:
2614             if (rgui->thumbnail_queue_size > 0)
2615                rgui->thumbnail_queue_size--;
2616             break;
2617          case MENU_IMAGE_LEFT_THUMBNAIL:
2618             if (rgui->left_thumbnail_queue_size > 0)
2619                rgui->left_thumbnail_queue_size--;
2620             break;
2621          case MENU_IMAGE_NONE:
2622          default:
2623             break;
2624       }
2625 
2626       return false;
2627    }
2628 
2629    switch (type)
2630    {
2631       case MENU_IMAGE_WALLPAPER:
2632          {
2633             struct texture_image *image = (struct texture_image*)data;
2634             process_wallpaper(rgui, image);
2635          }
2636          break;
2637       case MENU_IMAGE_THUMBNAIL:
2638          {
2639             struct texture_image *image = (struct texture_image*)data;
2640 
2641             if (rgui->show_fs_thumbnail)
2642                process_thumbnail(rgui, &rgui->fs_thumbnail, &rgui->thumbnail_queue_size, image);
2643             else if (settings->bools.menu_rgui_inline_thumbnails)
2644                process_thumbnail(rgui, &rgui->mini_thumbnail, &rgui->thumbnail_queue_size, image);
2645             else
2646             {
2647                /* If user toggles settings rapidly on very slow systems,
2648                 * it is possible for a thumbnail to be requested without
2649                 * it ever being processed. In this case, we still have to
2650                 * decrement the thumbnail queue (otherwise image updates
2651                 * will get 'stuck') */
2652                if (rgui->thumbnail_queue_size > 0)
2653                   rgui->thumbnail_queue_size--;
2654             }
2655          }
2656          break;
2657       case MENU_IMAGE_LEFT_THUMBNAIL:
2658          {
2659             struct texture_image *image = (struct texture_image*)data;
2660             process_thumbnail(rgui, &rgui->mini_left_thumbnail, &rgui->left_thumbnail_queue_size, image);
2661          }
2662          break;
2663       default:
2664          break;
2665    }
2666 
2667    return true;
2668 }
2669 
rgui_render_background(rgui_t * rgui,unsigned fb_width,unsigned fb_height,size_t fb_pitch)2670 static void rgui_render_background(rgui_t *rgui,
2671       unsigned fb_width, unsigned fb_height,
2672       size_t fb_pitch)
2673 {
2674    frame_buf_t *frame_buf      = &rgui->frame_buf;
2675    frame_buf_t *background_buf = &rgui->background_buf;
2676 
2677    /* Sanity check */
2678    if (!frame_buf->data ||
2679        (fb_width != frame_buf->width) ||
2680        (fb_height != frame_buf->height) ||
2681        (fb_pitch != frame_buf->width << 1))
2682       return;
2683 
2684    /* If screensaver is active, 'zero out' framebuffer */
2685    if (rgui->show_screensaver)
2686    {
2687       size_t i;
2688       uint16_t ss_bg_color    = rgui->colors.ss_bg_color;
2689       uint16_t *frame_buf_ptr = frame_buf->data;
2690 
2691       for (i = 0; i < frame_buf->width * frame_buf->height; i++)
2692          *(frame_buf_ptr++) = ss_bg_color;
2693    }
2694    /* Otherwise copy background to framebuffer */
2695    else if (background_buf->data)
2696       memcpy(frame_buf->data, background_buf->data,
2697             (size_t)frame_buf->width * (size_t)frame_buf->height * sizeof(uint16_t));
2698 }
2699 
rgui_render_fs_thumbnail(rgui_t * rgui,unsigned fb_width,unsigned fb_height,size_t fb_pitch)2700 static void rgui_render_fs_thumbnail(rgui_t *rgui,
2701       unsigned fb_width, unsigned fb_height, size_t fb_pitch)
2702 {
2703    uint16_t *frame_buf_data    = rgui->frame_buf.data;
2704    uint16_t *fs_thumbnail_data = rgui->fs_thumbnail.data;
2705 
2706    if (rgui->fs_thumbnail.is_valid && frame_buf_data && fs_thumbnail_data)
2707    {
2708       unsigned y;
2709       unsigned fb_x_offset, fb_y_offset;
2710       unsigned thumb_x_offset, thumb_y_offset;
2711       unsigned width, height;
2712       unsigned fs_thumbnail_width  = rgui->fs_thumbnail.width;
2713       unsigned fs_thumbnail_height = rgui->fs_thumbnail.height;
2714       uint16_t *src                = NULL;
2715       uint16_t *dst                = NULL;
2716 
2717       /* Ensure that thumbnail is centred
2718        * > Have to perform some stupid tests here because we
2719        *   cannot assume fb_width and fb_height are constant and
2720        *   >= thumbnail.width and thumbnail.height (even though
2721        *   they are...) */
2722       if (fs_thumbnail_width <= fb_width)
2723       {
2724          thumb_x_offset = 0;
2725          fb_x_offset    = (fb_width - fs_thumbnail_width) >> 1;
2726          width          = fs_thumbnail_width;
2727       }
2728       else
2729       {
2730          thumb_x_offset = (fs_thumbnail_width - fb_width) >> 1;
2731          fb_x_offset    = 0;
2732          width          = fb_width;
2733       }
2734       if (fs_thumbnail_height <= fb_height)
2735       {
2736          thumb_y_offset = 0;
2737          fb_y_offset    = (fb_height - fs_thumbnail_height) >> 1;
2738          height         = fs_thumbnail_height;
2739       }
2740       else
2741       {
2742          thumb_y_offset = (fs_thumbnail_height - fb_height) >> 1;
2743          fb_y_offset    = 0;
2744          height         = fb_height;
2745       }
2746 
2747       /* Copy thumbnail to framebuffer */
2748       for (y = 0; y < height; y++)
2749       {
2750          src = fs_thumbnail_data + thumb_x_offset + ((y + thumb_y_offset) * fs_thumbnail_width);
2751          dst = frame_buf_data + (y + fb_y_offset) * (fb_pitch >> 1) + fb_x_offset;
2752 
2753          memcpy(dst, src, width * sizeof(uint16_t));
2754       }
2755 
2756       /* Draw drop shadow, if required */
2757       if (rgui->shadow_enable)
2758       {
2759          unsigned shadow_x;
2760          unsigned shadow_y;
2761          unsigned shadow_width;
2762          unsigned shadow_height;
2763 
2764          /* Vertical component */
2765          if (fs_thumbnail_width < fb_width)
2766          {
2767             shadow_width  = fb_width - fs_thumbnail_width;
2768             shadow_width  = shadow_width > 2 ? 2 : shadow_width;
2769             shadow_height = fs_thumbnail_height + 2 < fb_height ? fs_thumbnail_height : fb_height - 2;
2770 
2771             shadow_x      = fb_x_offset + fs_thumbnail_width;
2772             shadow_y      = fb_y_offset + 2;
2773 
2774             rgui_color_rect(frame_buf_data, fb_width, fb_height,
2775                   shadow_x, shadow_y, shadow_width, shadow_height, rgui->colors.shadow_color);
2776          }
2777 
2778          /* Horizontal component */
2779          if (fs_thumbnail_height < fb_height)
2780          {
2781             shadow_height = fb_height - fs_thumbnail_height;
2782             shadow_height = shadow_height > 2 ? 2 : shadow_height;
2783             shadow_width  = fs_thumbnail_width + 2 < fb_width ? fs_thumbnail_width : fb_width - 2;
2784 
2785             shadow_x      = fb_x_offset + 2;
2786             shadow_y      = fb_y_offset + fs_thumbnail_height;
2787 
2788             rgui_color_rect(frame_buf_data, fb_width, fb_height,
2789                   shadow_x, shadow_y, shadow_width, shadow_height, rgui->colors.shadow_color);
2790          }
2791       }
2792    }
2793 }
2794 
rgui_get_mini_thumbnail_fullwidth(rgui_t * rgui)2795 static unsigned INLINE rgui_get_mini_thumbnail_fullwidth(rgui_t *rgui)
2796 {
2797    unsigned width      = rgui->mini_thumbnail.is_valid ? rgui->mini_thumbnail.width : 0;
2798    unsigned left_width = rgui->mini_left_thumbnail.is_valid ? rgui->mini_left_thumbnail.width : 0;
2799    return width >= left_width ? width : left_width;
2800 }
2801 
rgui_render_mini_thumbnail(rgui_t * rgui,thumbnail_t * thumbnail,enum gfx_thumbnail_id thumbnail_id,unsigned fb_width,unsigned fb_height,size_t fb_pitch)2802 static void rgui_render_mini_thumbnail(
2803       rgui_t *rgui, thumbnail_t *thumbnail, enum gfx_thumbnail_id thumbnail_id,
2804       unsigned fb_width, unsigned fb_height,
2805       size_t fb_pitch)
2806 {
2807    settings_t *settings     = config_get_ptr();
2808    uint16_t *frame_buf_data = rgui->frame_buf.data;
2809 
2810    if (!thumbnail || !settings)
2811       return;
2812 
2813    if (thumbnail->is_valid && frame_buf_data && thumbnail->data)
2814    {
2815       unsigned y;
2816       unsigned fb_x_offset, fb_y_offset;
2817       unsigned thumbnail_fullwidth = rgui_get_mini_thumbnail_fullwidth(rgui);
2818       uint16_t *src                = NULL;
2819       uint16_t *dst                = NULL;
2820       unsigned term_width          = rgui->term_layout.width * rgui->font_width_stride;
2821       unsigned term_height         = rgui->term_layout.height * rgui->font_height_stride;
2822 
2823       /* Sanity check (this can never, ever happen, so just return
2824        * instead of trying to crop the thumbnail image...) */
2825       if (     (thumbnail_fullwidth > term_width)
2826             || (thumbnail->height   > term_height))
2827          return;
2828 
2829       fb_x_offset = (rgui->term_layout.start_x + term_width) -
2830             (thumbnail->width + ((thumbnail_fullwidth - thumbnail->width) >> 1));
2831 
2832       if (((thumbnail_id == GFX_THUMBNAIL_RIGHT) && !settings->bools.menu_rgui_swap_thumbnails) ||
2833           ((thumbnail_id == GFX_THUMBNAIL_LEFT)  && settings->bools.menu_rgui_swap_thumbnails))
2834          fb_y_offset = rgui->term_layout.start_y + ((thumbnail->max_height - thumbnail->height) >> 1);
2835       else
2836          fb_y_offset = (rgui->term_layout.start_y + term_height) -
2837                (thumbnail->height + ((thumbnail->max_height - thumbnail->height) >> 1));
2838 
2839       /* Copy thumbnail to framebuffer */
2840       for (y = 0; y < thumbnail->height; y++)
2841       {
2842          src = thumbnail->data + (y * thumbnail->width);
2843          dst = frame_buf_data + (y + fb_y_offset) *
2844                (fb_pitch >> 1) + fb_x_offset;
2845 
2846          memcpy(dst, src, thumbnail->width * sizeof(uint16_t));
2847       }
2848 
2849       /* Draw drop shadow, if required */
2850       if (rgui->shadow_enable)
2851       {
2852          rgui_color_rect(frame_buf_data, fb_width, fb_height,
2853                fb_x_offset + thumbnail->width, fb_y_offset + 1,
2854                1, thumbnail->height, rgui->colors.shadow_color);
2855          rgui_color_rect(frame_buf_data, fb_width, fb_height,
2856                fb_x_offset + 1, fb_y_offset + thumbnail->height,
2857                thumbnail->width, 1, rgui->colors.shadow_color);
2858       }
2859    }
2860 }
2861 
get_theme(rgui_t * rgui)2862 static const rgui_theme_t *get_theme(rgui_t *rgui)
2863 {
2864    bool transparent = rgui->transparency_supported &&
2865          rgui->transparency_enable;
2866 
2867    switch (rgui->color_theme)
2868    {
2869       case RGUI_THEME_CLASSIC_RED:
2870          return transparent ?
2871                &rgui_theme_classic_red :
2872                &rgui_theme_opaque_classic_red;
2873       case RGUI_THEME_CLASSIC_ORANGE:
2874          return transparent ?
2875                &rgui_theme_classic_orange :
2876                &rgui_theme_opaque_classic_orange;
2877       case RGUI_THEME_CLASSIC_YELLOW:
2878          return transparent ?
2879                &rgui_theme_classic_yellow :
2880                &rgui_theme_opaque_classic_yellow;
2881       case RGUI_THEME_CLASSIC_GREEN:
2882          return transparent ?
2883                &rgui_theme_classic_green :
2884                &rgui_theme_opaque_classic_green;
2885       case RGUI_THEME_CLASSIC_BLUE:
2886          return transparent ?
2887                &rgui_theme_classic_blue :
2888                &rgui_theme_opaque_classic_blue;
2889       case RGUI_THEME_CLASSIC_VIOLET:
2890          return transparent ?
2891                &rgui_theme_classic_violet :
2892                &rgui_theme_opaque_classic_violet;
2893       case RGUI_THEME_CLASSIC_GREY:
2894          return transparent ?
2895                &rgui_theme_classic_grey :
2896                &rgui_theme_opaque_classic_grey;
2897       case RGUI_THEME_LEGACY_RED:
2898          return transparent ?
2899                &rgui_theme_legacy_red :
2900                &rgui_theme_opaque_legacy_red;
2901       case RGUI_THEME_DARK_PURPLE:
2902          return transparent ?
2903                &rgui_theme_dark_purple :
2904                &rgui_theme_opaque_dark_purple;
2905       case RGUI_THEME_MIDNIGHT_BLUE:
2906          return transparent ?
2907                &rgui_theme_midnight_blue :
2908                &rgui_theme_opaque_midnight_blue;
2909       case RGUI_THEME_GOLDEN:
2910          return transparent ?
2911                &rgui_theme_golden :
2912                &rgui_theme_opaque_golden;
2913       case RGUI_THEME_ELECTRIC_BLUE:
2914          return transparent ?
2915                &rgui_theme_electric_blue :
2916                &rgui_theme_opaque_electric_blue;
2917       case RGUI_THEME_APPLE_GREEN:
2918          return transparent ?
2919                &rgui_theme_apple_green :
2920                &rgui_theme_opaque_apple_green;
2921       case RGUI_THEME_VOLCANIC_RED:
2922          return transparent ?
2923                &rgui_theme_volcanic_red :
2924                &rgui_theme_opaque_volcanic_red;
2925       case RGUI_THEME_LAGOON:
2926          return transparent ?
2927                &rgui_theme_lagoon :
2928                &rgui_theme_opaque_lagoon;
2929       case RGUI_THEME_BROGRAMMER:
2930          return transparent ?
2931                &rgui_theme_brogrammer :
2932                &rgui_theme_opaque_brogrammer;
2933       case RGUI_THEME_DRACULA:
2934          return transparent ?
2935                &rgui_theme_dracula :
2936                &rgui_theme_opaque_dracula;
2937       case RGUI_THEME_FAIRYFLOSS:
2938          return transparent ?
2939                &rgui_theme_fairyfloss :
2940                &rgui_theme_opaque_fairyfloss;
2941       case RGUI_THEME_FLATUI:
2942          return transparent ?
2943                &rgui_theme_flatui :
2944                &rgui_theme_opaque_flatui;
2945       case RGUI_THEME_GRUVBOX_DARK:
2946          return transparent ?
2947                &rgui_theme_gruvbox_dark :
2948                &rgui_theme_opaque_gruvbox_dark;
2949       case RGUI_THEME_GRUVBOX_LIGHT:
2950          return transparent ?
2951                &rgui_theme_gruvbox_light :
2952                &rgui_theme_opaque_gruvbox_light;
2953       case RGUI_THEME_HACKING_THE_KERNEL:
2954          return transparent ?
2955                &rgui_theme_hacking_the_kernel :
2956                &rgui_theme_opaque_hacking_the_kernel;
2957       case RGUI_THEME_NORD:
2958          return transparent ?
2959                &rgui_theme_nord :
2960                &rgui_theme_opaque_nord;
2961       case RGUI_THEME_NOVA:
2962          return transparent ?
2963                &rgui_theme_nova :
2964                &rgui_theme_opaque_nova;
2965       case RGUI_THEME_ONE_DARK:
2966          return transparent ?
2967                &rgui_theme_one_dark :
2968                &rgui_theme_opaque_one_dark;
2969       case RGUI_THEME_PALENIGHT:
2970          return transparent ?
2971                &rgui_theme_palenight :
2972                &rgui_theme_opaque_palenight;
2973       case RGUI_THEME_SOLARIZED_DARK:
2974          return transparent ?
2975                &rgui_theme_solarized_dark :
2976                &rgui_theme_opaque_solarized_dark;
2977       case RGUI_THEME_SOLARIZED_LIGHT:
2978          return transparent ?
2979                &rgui_theme_solarized_light :
2980                &rgui_theme_opaque_solarized_light;
2981       case RGUI_THEME_TANGO_DARK:
2982          return transparent ?
2983                &rgui_theme_tango_dark :
2984                &rgui_theme_opaque_tango_dark;
2985       case RGUI_THEME_TANGO_LIGHT:
2986          return transparent ?
2987                &rgui_theme_tango_light :
2988                &rgui_theme_opaque_tango_light;
2989       case RGUI_THEME_ZENBURN:
2990          return transparent ?
2991                &rgui_theme_zenburn :
2992                &rgui_theme_opaque_zenburn;
2993       case RGUI_THEME_ANTI_ZENBURN:
2994          return transparent ?
2995                &rgui_theme_anti_zenburn :
2996                &rgui_theme_opaque_anti_zenburn;
2997       case RGUI_THEME_FLUX:
2998          return transparent ?
2999                &rgui_theme_flux :
3000                &rgui_theme_opaque_flux;
3001       default:
3002          break;
3003    }
3004 
3005    return transparent ?
3006          &rgui_theme_classic_green :
3007          &rgui_theme_opaque_classic_green;
3008 }
3009 
load_custom_theme(rgui_t * rgui,rgui_theme_t * theme_colors,const char * theme_path)3010 static void load_custom_theme(rgui_t *rgui, rgui_theme_t *theme_colors, const char *theme_path)
3011 {
3012    char wallpaper_file[PATH_MAX_LENGTH];
3013    unsigned normal_color       = 0;
3014    unsigned hover_color        = 0;
3015    unsigned title_color        = 0;
3016    unsigned bg_dark_color      = 0;
3017    unsigned bg_light_color     = 0;
3018    unsigned border_dark_color  = 0;
3019    unsigned border_light_color = 0;
3020    unsigned shadow_color       = 0;
3021    unsigned particle_color     = 0;
3022    config_file_t *conf         = NULL;
3023    const char *wallpaper_key   = NULL;
3024    bool success                = false;
3025 #if defined(DINGUX)
3026    unsigned aspect_ratio       = RGUI_DINGUX_ASPECT_RATIO;
3027 #else
3028    settings_t *settings        = config_get_ptr();
3029    unsigned aspect_ratio       = settings->uints.menu_rgui_aspect_ratio;
3030 #endif
3031 
3032    /* Determine which type of wallpaper to load */
3033    switch (aspect_ratio)
3034    {
3035       case RGUI_ASPECT_RATIO_16_9:
3036       case RGUI_ASPECT_RATIO_16_9_CENTRE:
3037          wallpaper_key = "rgui_wallpaper_16_9";
3038          break;
3039       case RGUI_ASPECT_RATIO_16_10:
3040       case RGUI_ASPECT_RATIO_16_10_CENTRE:
3041          wallpaper_key = "rgui_wallpaper_16_10";
3042          break;
3043       case RGUI_ASPECT_RATIO_3_2:
3044       case RGUI_ASPECT_RATIO_3_2_CENTRE:
3045          wallpaper_key = "rgui_wallpaper_3_2";
3046          break;
3047       case RGUI_ASPECT_RATIO_5_3:
3048       case RGUI_ASPECT_RATIO_5_3_CENTRE:
3049          wallpaper_key = "rgui_wallpaper_5_3";
3050          break;
3051       default:
3052          /* 4:3 */
3053          wallpaper_key = "rgui_wallpaper";
3054          break;
3055    }
3056 
3057    wallpaper_file[0] = '\0';
3058 
3059    /* Sanity check */
3060    if (string_is_empty(theme_path))
3061       goto end;
3062    if (!path_is_valid(theme_path))
3063       goto end;
3064 
3065    /* Open config file */
3066    if (!(conf = config_file_new_from_path_to_string(theme_path)))
3067       goto end;
3068 
3069    /* Parse config file */
3070    if (!config_get_hex(conf, "rgui_entry_normal_color", &normal_color))
3071       goto end;
3072 
3073    if (!config_get_hex(conf, "rgui_entry_hover_color", &hover_color))
3074       goto end;
3075 
3076    if (!config_get_hex(conf, "rgui_title_color", &title_color))
3077       goto end;
3078 
3079    if (!config_get_hex(conf, "rgui_bg_dark_color", &bg_dark_color))
3080       goto end;
3081 
3082    if (!config_get_hex(conf, "rgui_bg_light_color", &bg_light_color))
3083       goto end;
3084 
3085    if (!config_get_hex(conf, "rgui_border_dark_color", &border_dark_color))
3086       goto end;
3087 
3088    if (!config_get_hex(conf, "rgui_border_light_color", &border_light_color))
3089       goto end;
3090 
3091    /* Make shadow colour optional (fallback to fully opaque black)
3092     * - i.e. if user has no intention of enabling shadows, they
3093     * should not have to include this entry */
3094    if (!config_get_hex(conf, "rgui_shadow_color", &shadow_color))
3095       shadow_color = 0xFF000000;
3096 
3097    /* Make particle colour optional too (fallback to normal
3098     * rgb with bg_light alpha) */
3099    if (!config_get_hex(conf, "rgui_particle_color", &particle_color))
3100       particle_color = (normal_color & 0x00FFFFFF) |
3101                        (bg_light_color & 0xFF000000);
3102 
3103    config_get_array(conf, wallpaper_key,
3104          wallpaper_file, sizeof(wallpaper_file));
3105 
3106    success = true;
3107 
3108 end:
3109 
3110    if (success)
3111    {
3112       theme_colors->normal_color       = (uint32_t)normal_color;
3113       theme_colors->hover_color        = (uint32_t)hover_color;
3114       theme_colors->title_color        = (uint32_t)title_color;
3115       theme_colors->bg_dark_color      = (uint32_t)bg_dark_color;
3116       theme_colors->bg_light_color     = (uint32_t)bg_light_color;
3117       theme_colors->border_dark_color  = (uint32_t)border_dark_color;
3118       theme_colors->border_light_color = (uint32_t)border_light_color;
3119       theme_colors->shadow_color       = (uint32_t)shadow_color;
3120       theme_colors->particle_color     = (uint32_t)particle_color;
3121 
3122       /* Load wallpaper, if required */
3123       if (!string_is_empty(wallpaper_file))
3124       {
3125          char wallpaper_path[PATH_MAX_LENGTH];
3126          wallpaper_path[0] = '\0';
3127 
3128          fill_pathname_resolve_relative(wallpaper_path, theme_path, wallpaper_file, sizeof(wallpaper_path));
3129          /* Ensure that path is valid... */
3130          if (path_is_valid(wallpaper_path))
3131             /* Unlike thumbnails, we don't worry about queued images
3132              * here - in general, wallpaper is loaded once per session
3133              * and then forgotten, so performance issues are not a concern */
3134             task_push_image_load(wallpaper_path,
3135                   video_driver_supports_rgba(), 0,
3136                   menu_display_handle_wallpaper_upload, NULL);
3137       }
3138    }
3139    else
3140    {
3141       /* Use 'Classic Green' fallback */
3142       const rgui_theme_t *fallback_theme =
3143             (rgui->transparency_supported && rgui->transparency_enable) ?
3144                   &rgui_theme_classic_green : &rgui_theme_opaque_classic_green;
3145 
3146       theme_colors->normal_color       = fallback_theme->normal_color;
3147       theme_colors->hover_color        = fallback_theme->hover_color;
3148       theme_colors->title_color        = fallback_theme->title_color;
3149       theme_colors->bg_dark_color      = fallback_theme->bg_dark_color;
3150       theme_colors->bg_light_color     = fallback_theme->bg_light_color;
3151       theme_colors->border_dark_color  = fallback_theme->border_dark_color;
3152       theme_colors->border_light_color = fallback_theme->border_light_color;
3153       theme_colors->shadow_color       = fallback_theme->shadow_color;
3154       theme_colors->particle_color     = fallback_theme->particle_color;
3155    }
3156 
3157    if (conf)
3158       config_file_free(conf);
3159    conf = NULL;
3160 }
3161 
rgui_cache_background(rgui_t * rgui,unsigned fb_width,unsigned fb_height,size_t fb_pitch)3162 static void rgui_cache_background(rgui_t *rgui,
3163       unsigned fb_width, unsigned fb_height, size_t fb_pitch)
3164 {
3165    frame_buf_t *background_buf = &rgui->background_buf;
3166 
3167    /* Only regenerate the background if we are *not*
3168     * currently showing a wallpaper image */
3169    if (rgui->show_wallpaper)
3170       return;
3171    /* Sanity check */
3172    if ((fb_width  != background_buf->width)      ||
3173        (fb_height != background_buf->height)     ||
3174        (fb_pitch  != background_buf->width << 1) ||
3175        !background_buf->data)
3176       return;
3177 
3178    /* Fill background buffer with standard chequer pattern */
3179    rgui_fill_rect(background_buf->data, fb_width, fb_height,
3180          0, 0, fb_width, fb_height,
3181          rgui->colors.bg_dark_color, rgui->colors.bg_light_color, rgui->bg_thickness);
3182 
3183    /* Draw border, if required */
3184    if (rgui->border_enable)
3185       rgui_render_border(rgui, background_buf->data, fb_width, fb_height);
3186 }
3187 
prepare_rgui_colors(rgui_t * rgui,settings_t * settings)3188 static void prepare_rgui_colors(rgui_t *rgui, settings_t *settings)
3189 {
3190    rgui_theme_t theme_colors;
3191    uint32_t ss_particle_color_argb32;
3192    unsigned rgui_color_theme     = settings->uints.menu_rgui_color_theme;
3193    const char *rgui_theme_preset = settings->paths.path_rgui_theme_preset;
3194    bool rgui_transparency        = settings->bools.menu_rgui_transparency;
3195 
3196    rgui->color_theme             = rgui_color_theme;
3197    rgui->transparency_enable     = rgui_transparency;
3198    rgui->show_wallpaper          = false;
3199 
3200    if (rgui->color_theme == RGUI_THEME_CUSTOM)
3201    {
3202       memcpy(rgui->theme_preset_path,
3203             rgui_theme_preset, sizeof(rgui->theme_preset_path));
3204       load_custom_theme(rgui, &theme_colors, rgui_theme_preset);
3205    }
3206    else
3207    {
3208       const rgui_theme_t *current_theme = get_theme(rgui);
3209 
3210       theme_colors.hover_color          = current_theme->hover_color;
3211       theme_colors.normal_color         = current_theme->normal_color;
3212       theme_colors.title_color          = current_theme->title_color;
3213       theme_colors.bg_dark_color        = current_theme->bg_dark_color;
3214       theme_colors.bg_light_color       = current_theme->bg_light_color;
3215       theme_colors.border_dark_color    = current_theme->border_dark_color;
3216       theme_colors.border_light_color   = current_theme->border_light_color;
3217       theme_colors.shadow_color         = current_theme->shadow_color;
3218       theme_colors.particle_color       = current_theme->particle_color;
3219    }
3220 
3221    rgui->colors.hover_color             = argb32_to_pixel_platform_format(theme_colors.hover_color);
3222    rgui->colors.normal_color            = argb32_to_pixel_platform_format(theme_colors.normal_color);
3223    rgui->colors.title_color             = argb32_to_pixel_platform_format(theme_colors.title_color);
3224    rgui->colors.bg_dark_color           = argb32_to_pixel_platform_format(theme_colors.bg_dark_color);
3225    rgui->colors.bg_light_color          = argb32_to_pixel_platform_format(theme_colors.bg_light_color);
3226    rgui->colors.border_dark_color       = argb32_to_pixel_platform_format(theme_colors.border_dark_color);
3227    rgui->colors.border_light_color      = argb32_to_pixel_platform_format(theme_colors.border_light_color);
3228    rgui->colors.shadow_color            = argb32_to_pixel_platform_format(theme_colors.shadow_color);
3229    rgui->colors.particle_color          = argb32_to_pixel_platform_format(theme_colors.particle_color);
3230 
3231    /* Screensaver background is black, 100% opacity */
3232    rgui->colors.ss_bg_color             = argb32_to_pixel_platform_format(0xFF000000);
3233    /* Screensaver particles are a 75:25 mix of
3234     * regular background animation particle colour
3235     * and black, with 100% opacity */
3236    ss_particle_color_argb32             = (theme_colors.particle_color +
3237          (theme_colors.particle_color & 0x1010101)) >> 1;
3238    ss_particle_color_argb32             = (theme_colors.particle_color +
3239          ss_particle_color_argb32 +
3240                ((theme_colors.particle_color ^ ss_particle_color_argb32) & 0x1010101)) >> 1;
3241    rgui->colors.ss_particle_color       = argb32_to_pixel_platform_format(
3242          ss_particle_color_argb32 | 0xFF000000);
3243 
3244    rgui->bg_modified                    = true;
3245    rgui->force_redraw                   = true;
3246 }
3247 
3248 /* ==============================
3249  * blit_line/symbol() START
3250  * ============================== */
3251 
3252 /* NOTE 1: These functions are WET (Write Everything Twice).
3253  * This is bad design and difficult to maintain, but we have
3254  * no other choice here. blit_line() is so performance
3255  * critical that we simply cannot afford to check user
3256  * settings internally. */
3257 
3258 /* NOTE 2: We should really be using:
3259  *  - rgui->font_width
3260  *  - rgui->font_height
3261  *  - rgui->font_width_stride
3262  * ...in these functions. This would ensure compatibility
3263  * with any future font modifications, but unfortunately
3264  * this kind of memory access has a catastrophic performance
3265  * impact. We therefore have to use the raw defines instead:
3266  * > For regular/extended blitting:
3267  *   - FONT_WIDTH
3268  *   - FONT_HEIGHT
3269  *   - FONT_WIDTH_STRIDE
3270  * > For CJK blitting:
3271  *   - FONT_10X10_WIDTH
3272  *   - FONT_10X10_HEIGHT
3273  *   - FONT_10X10_WIDTH_STRIDE
3274  * This is 'safe', because we have absolute control over
3275  * which blitting function is used when specific fonts
3276  * are 'active' - but other devs should be very careful
3277  * when adding new fonts in the future */
3278 
3279 /* blit_line() */
3280 
blit_line_regular(rgui_t * rgui,unsigned fb_width,int x,int y,const char * message,uint16_t color,uint16_t shadow_color)3281 static void blit_line_regular(
3282       rgui_t *rgui,
3283       unsigned fb_width, int x, int y,
3284       const char *message, uint16_t color, uint16_t shadow_color)
3285 {
3286    uint16_t *frame_buf_data = rgui->frame_buf.data;
3287    bool **lut               = rgui->fonts.regular->lut;
3288 
3289    while (!string_is_empty(message))
3290    {
3291       unsigned i, j;
3292       uint8_t symbol = (uint8_t)*message++;
3293 
3294       if (symbol >= RGUI_NUM_FONT_GLYPHS_REGULAR)
3295          continue;
3296 
3297       if (symbol != ' ')
3298       {
3299          bool *symbol_lut = lut[symbol];
3300 
3301          for (j = 0; j < FONT_HEIGHT; j++)
3302          {
3303             unsigned buff_offset = ((y + j) * fb_width) + x;
3304 
3305             for (i = 0; i < FONT_WIDTH; i++)
3306             {
3307                if (*(symbol_lut + i + (j * FONT_WIDTH)))
3308                   *(frame_buf_data + buff_offset + i) = color;
3309             }
3310          }
3311       }
3312 
3313       x += FONT_WIDTH_STRIDE;
3314    }
3315 }
3316 
blit_line_regular_shadow(rgui_t * rgui,unsigned fb_width,int x,int y,const char * message,uint16_t color,uint16_t shadow_color)3317 static void blit_line_regular_shadow(
3318       rgui_t *rgui,
3319       unsigned fb_width, int x, int y,
3320       const char *message, uint16_t color, uint16_t shadow_color)
3321 {
3322    uint16_t *frame_buf_data = rgui->frame_buf.data;
3323    bool **lut               = rgui->fonts.regular->lut;
3324    uint16_t color_buf[2];
3325    uint16_t shadow_color_buf[2];
3326 
3327    color_buf[0] = color;
3328    color_buf[1] = shadow_color;
3329 
3330    shadow_color_buf[0] = shadow_color;
3331    shadow_color_buf[1] = shadow_color;
3332 
3333    while (!string_is_empty(message))
3334    {
3335       unsigned i, j;
3336       uint8_t symbol = (uint8_t)*message++;
3337 
3338       if (symbol >= RGUI_NUM_FONT_GLYPHS_REGULAR)
3339          continue;
3340 
3341       if (symbol != ' ')
3342       {
3343          bool *symbol_lut = lut[symbol];
3344 
3345          for (j = 0; j < FONT_HEIGHT; j++)
3346          {
3347             unsigned buff_offset = ((y + j) * fb_width) + x;
3348 
3349             for (i = 0; i < FONT_WIDTH; i++)
3350             {
3351                if (*(symbol_lut + i + (j * FONT_WIDTH)))
3352                {
3353                   uint16_t *frame_buf_ptr = frame_buf_data + buff_offset + i;
3354 
3355                   /* Text pixel + right shadow */
3356                   memcpy(frame_buf_ptr, color_buf, sizeof(color_buf));
3357 
3358                   /* Bottom shadow */
3359                   frame_buf_ptr += fb_width;
3360                   memcpy(frame_buf_ptr, shadow_color_buf, sizeof(shadow_color_buf));
3361                }
3362             }
3363          }
3364       }
3365 
3366       x += FONT_WIDTH_STRIDE;
3367    }
3368 }
3369 
blit_line_extended(rgui_t * rgui,unsigned fb_width,int x,int y,const char * message,uint16_t color,uint16_t shadow_color)3370 static void blit_line_extended(
3371       rgui_t *rgui,
3372       unsigned fb_width, int x, int y,
3373       const char *message, uint16_t color, uint16_t shadow_color)
3374 {
3375    uint16_t *frame_buf_data = rgui->frame_buf.data;
3376    bool **lut               = rgui->fonts.regular->lut;
3377 
3378    while (!string_is_empty(message))
3379    {
3380       /* Deal with spaces first, for efficiency */
3381       if (*message == ' ')
3382          message++;
3383       else
3384       {
3385          unsigned i, j;
3386          bool *symbol_lut;
3387          uint32_t symbol = utf8_walk(&message);
3388 
3389          /* Stupid cretinous hack: 'oe' ligatures are not
3390           * really standard extended ASCII, so we have to
3391           * waste CPU cycles performing a conversion from
3392           * the unicode values...
3393           * (Note: This is only really required for msg_hash_fr.h) */
3394          if (symbol == 339) /* Latin small ligature oe */
3395             symbol = 156;
3396          if (symbol == 338) /* Latin capital ligature oe */
3397             symbol = 140;
3398 
3399          if (symbol >= RGUI_NUM_FONT_GLYPHS_EXTENDED)
3400             continue;
3401 
3402          symbol_lut = lut[symbol];
3403 
3404          for (j = 0; j < FONT_HEIGHT; j++)
3405          {
3406             unsigned buff_offset = ((y + j) * fb_width) + x;
3407 
3408             for (i = 0; i < FONT_WIDTH; i++)
3409             {
3410                if (*(symbol_lut + i + (j * FONT_WIDTH)))
3411                   *(frame_buf_data + buff_offset + i) = color;
3412             }
3413          }
3414       }
3415 
3416       x += FONT_WIDTH_STRIDE;
3417    }
3418 }
3419 
blit_line_extended_shadow(rgui_t * rgui,unsigned fb_width,int x,int y,const char * message,uint16_t color,uint16_t shadow_color)3420 static void blit_line_extended_shadow(
3421       rgui_t *rgui,
3422       unsigned fb_width, int x, int y,
3423       const char *message, uint16_t color, uint16_t shadow_color)
3424 {
3425    uint16_t *frame_buf_data = rgui->frame_buf.data;
3426    bool **lut               = rgui->fonts.regular->lut;
3427    uint16_t color_buf[2];
3428    uint16_t shadow_color_buf[2];
3429 
3430    color_buf[0] = color;
3431    color_buf[1] = shadow_color;
3432 
3433    shadow_color_buf[0] = shadow_color;
3434    shadow_color_buf[1] = shadow_color;
3435 
3436    while (!string_is_empty(message))
3437    {
3438       /* Deal with spaces first, for efficiency */
3439       if (*message == ' ')
3440          message++;
3441       else
3442       {
3443          unsigned i, j;
3444          bool *symbol_lut;
3445          uint32_t symbol = utf8_walk(&message);
3446 
3447          /* Stupid cretinous hack: 'oe' ligatures are not
3448           * really standard extended ASCII, so we have to
3449           * waste CPU cycles performing a conversion from
3450           * the unicode values...
3451           * (Note: This is only really required for msg_hash_fr.h) */
3452          if (symbol == 339) /* Latin small ligature oe */
3453             symbol = 156;
3454          if (symbol == 338) /* Latin capital ligature oe */
3455             symbol = 140;
3456 
3457          if (symbol >= RGUI_NUM_FONT_GLYPHS_EXTENDED)
3458             continue;
3459 
3460          symbol_lut = lut[symbol];
3461 
3462          for (j = 0; j < FONT_HEIGHT; j++)
3463          {
3464             unsigned buff_offset = ((y + j) * fb_width) + x;
3465 
3466             for (i = 0; i < FONT_WIDTH; i++)
3467             {
3468                if (*(symbol_lut + i + (j * FONT_WIDTH)))
3469                {
3470                   uint16_t *frame_buf_ptr = frame_buf_data + buff_offset + i;
3471 
3472                   /* Text pixel + right shadow */
3473                   memcpy(frame_buf_ptr, color_buf, sizeof(color_buf));
3474 
3475                   /* Bottom shadow */
3476                   frame_buf_ptr += fb_width;
3477                   memcpy(frame_buf_ptr, shadow_color_buf, sizeof(shadow_color_buf));
3478                }
3479             }
3480          }
3481       }
3482 
3483       x += FONT_WIDTH_STRIDE;
3484    }
3485 }
3486 
3487 #ifdef HAVE_LANGEXTRA
blit_line_cjk(rgui_t * rgui,unsigned fb_width,int x,int y,const char * message,uint16_t color,uint16_t shadow_color)3488 static void blit_line_cjk(
3489       rgui_t *rgui,
3490       unsigned fb_width, int x, int y,
3491       const char *message, uint16_t color, uint16_t shadow_color)
3492 {
3493    uint16_t *frame_buf_data   = rgui->frame_buf.data;
3494    bitmapfont_lut_t *font_eng = rgui->fonts.eng_10x10;
3495    bitmapfont_lut_t *font_chn = rgui->fonts.chn_10x10;
3496    bitmapfont_lut_t *font_jpn = rgui->fonts.jpn_10x10;
3497    bitmapfont_lut_t *font_kor = rgui->fonts.kor_10x10;
3498 
3499    while (!string_is_empty(message))
3500    {
3501       /* Deal with spaces first, for efficiency */
3502       if (*message == ' ')
3503          message++;
3504       else
3505       {
3506          unsigned i, j;
3507          bool *symbol_lut;
3508          uint32_t symbol = utf8_walk(&message);
3509 
3510          if (symbol == 339) /* Latin small ligature oe */
3511             symbol = 156;
3512          if (symbol == 338) /* Latin capital ligature oe */
3513             symbol = 140;
3514 
3515          /* Find glyph LUT data */
3516          if (symbol <= font_eng->glyph_max)
3517             symbol_lut = font_eng->lut[symbol];
3518          else if ((symbol >= font_chn->glyph_min) && (symbol <= font_chn->glyph_max))
3519             symbol_lut = font_chn->lut[symbol - font_chn->glyph_min];
3520          else if ((symbol >= font_jpn->glyph_min) && (symbol <= font_jpn->glyph_max))
3521             symbol_lut = font_jpn->lut[symbol - font_jpn->glyph_min];
3522          else if ((symbol >= font_kor->glyph_min) && (symbol <= font_kor->glyph_max))
3523             symbol_lut = font_kor->lut[symbol - font_kor->glyph_min];
3524          else
3525             continue;
3526 
3527          for (j = 0; j < FONT_10X10_HEIGHT; j++)
3528          {
3529             unsigned buff_offset = ((y + j) * fb_width) + x;
3530 
3531             for (i = 0; i < FONT_10X10_WIDTH; i++)
3532             {
3533                if (*(symbol_lut + i + (j * FONT_10X10_WIDTH)))
3534                   *(frame_buf_data + buff_offset + i) = color;
3535             }
3536          }
3537       }
3538 
3539       x += FONT_10X10_WIDTH_STRIDE;
3540    }
3541 }
3542 
blit_line_cjk_shadow(rgui_t * rgui,unsigned fb_width,int x,int y,const char * message,uint16_t color,uint16_t shadow_color)3543 static void blit_line_cjk_shadow(
3544       rgui_t *rgui,
3545       unsigned fb_width, int x, int y,
3546       const char *message, uint16_t color, uint16_t shadow_color)
3547 {
3548    uint16_t *frame_buf_data   = rgui->frame_buf.data;
3549    bitmapfont_lut_t *font_eng = rgui->fonts.eng_10x10;
3550    bitmapfont_lut_t *font_chn = rgui->fonts.chn_10x10;
3551    bitmapfont_lut_t *font_jpn = rgui->fonts.jpn_10x10;
3552    bitmapfont_lut_t *font_kor = rgui->fonts.kor_10x10;
3553    uint16_t color_buf[2];
3554    uint16_t shadow_color_buf[2];
3555 
3556    color_buf[0] = color;
3557    color_buf[1] = shadow_color;
3558 
3559    shadow_color_buf[0] = shadow_color;
3560    shadow_color_buf[1] = shadow_color;
3561 
3562    while (!string_is_empty(message))
3563    {
3564       /* Deal with spaces first, for efficiency */
3565       if (*message == ' ')
3566          message++;
3567       else
3568       {
3569          unsigned i, j;
3570          bool *symbol_lut;
3571          uint32_t symbol = utf8_walk(&message);
3572 
3573          if (symbol == 339) /* Latin small ligature oe */
3574             symbol = 156;
3575          if (symbol == 338) /* Latin capital ligature oe */
3576             symbol = 140;
3577 
3578          /* Find glyph LUT data */
3579          if (symbol <= font_eng->glyph_max)
3580             symbol_lut = font_eng->lut[symbol];
3581          else if ((symbol >= font_chn->glyph_min) && (symbol <= font_chn->glyph_max))
3582             symbol_lut = font_chn->lut[symbol - font_chn->glyph_min];
3583          else if ((symbol >= font_jpn->glyph_min) && (symbol <= font_jpn->glyph_max))
3584             symbol_lut = font_jpn->lut[symbol - font_jpn->glyph_min];
3585          else if ((symbol >= font_kor->glyph_min) && (symbol <= font_kor->glyph_max))
3586             symbol_lut = font_kor->lut[symbol - font_kor->glyph_min];
3587          else
3588             continue;
3589 
3590          for (j = 0; j < FONT_10X10_HEIGHT; j++)
3591          {
3592             unsigned buff_offset = ((y + j) * fb_width) + x;
3593 
3594             for (i = 0; i < FONT_10X10_WIDTH; i++)
3595             {
3596                if (*(symbol_lut + i + (j * FONT_10X10_WIDTH)))
3597                {
3598                   uint16_t *frame_buf_ptr = frame_buf_data + buff_offset + i;
3599 
3600                   /* Text pixel + right shadow */
3601                   memcpy(frame_buf_ptr, color_buf, sizeof(color_buf));
3602 
3603                   /* Bottom shadow */
3604                   frame_buf_ptr += fb_width;
3605                   memcpy(frame_buf_ptr, shadow_color_buf, sizeof(shadow_color_buf));
3606                }
3607             }
3608          }
3609       }
3610 
3611       x += FONT_10X10_WIDTH_STRIDE;
3612    }
3613 }
3614 
blit_line_rus(rgui_t * rgui,unsigned fb_width,int x,int y,const char * message,uint16_t color,uint16_t shadow_color)3615 static void blit_line_rus(
3616       rgui_t *rgui,
3617       unsigned fb_width, int x, int y,
3618       const char *message, uint16_t color, uint16_t shadow_color)
3619 {
3620    uint16_t *frame_buf_data   = rgui->frame_buf.data;
3621    bitmapfont_lut_t *font_eng = rgui->fonts.eng_10x10;
3622    bitmapfont_lut_t *font_rus = rgui->fonts.rus_10x10;
3623 
3624    while (!string_is_empty(message))
3625    {
3626       /* Deal with spaces first, for efficiency */
3627       if (*message == ' ')
3628          message++;
3629       else
3630       {
3631          unsigned i, j;
3632          bool *symbol_lut;
3633          uint32_t symbol = utf8_walk(&message);
3634 
3635          if (symbol == 339) /* Latin small ligature oe */
3636             symbol = 156;
3637          if (symbol == 338) /* Latin capital ligature oe */
3638             symbol = 140;
3639 
3640          /* Find glyph LUT data */
3641          if (symbol <= font_eng->glyph_max)
3642             symbol_lut = font_eng->lut[symbol];
3643          else if ((symbol >= font_rus->glyph_min) && (symbol <= font_rus->glyph_max))
3644             symbol_lut = font_rus->lut[symbol - font_rus->glyph_min];
3645          else
3646             continue;
3647 
3648          for (j = 0; j < FONT_10X10_HEIGHT; j++)
3649          {
3650             unsigned buff_offset = ((y + j) * fb_width) + x;
3651 
3652             for (i = 0; i < FONT_10X10_WIDTH; i++)
3653             {
3654                if (*(symbol_lut + i + (j * FONT_10X10_WIDTH)))
3655                   *(frame_buf_data + buff_offset + i) = color;
3656             }
3657          }
3658       }
3659 
3660       x += FONT_10X10_WIDTH_STRIDE;
3661    }
3662 }
3663 
blit_line_rus_shadow(rgui_t * rgui,unsigned fb_width,int x,int y,const char * message,uint16_t color,uint16_t shadow_color)3664 static void blit_line_rus_shadow(
3665       rgui_t *rgui,
3666       unsigned fb_width, int x, int y,
3667       const char *message, uint16_t color, uint16_t shadow_color)
3668 {
3669    uint16_t *frame_buf_data   = rgui->frame_buf.data;
3670    bitmapfont_lut_t *font_eng = rgui->fonts.eng_10x10;
3671    bitmapfont_lut_t *font_rus = rgui->fonts.rus_10x10;
3672    uint16_t color_buf[2];
3673    uint16_t shadow_color_buf[2];
3674 
3675    color_buf[0] = color;
3676    color_buf[1] = shadow_color;
3677 
3678    shadow_color_buf[0] = shadow_color;
3679    shadow_color_buf[1] = shadow_color;
3680 
3681    while (!string_is_empty(message))
3682    {
3683       /* Deal with spaces first, for efficiency */
3684       if (*message == ' ')
3685          message++;
3686       else
3687       {
3688          unsigned i, j;
3689          bool *symbol_lut;
3690          uint32_t symbol = utf8_walk(&message);
3691 
3692          if (symbol == 339) /* Latin small ligature oe */
3693             symbol = 156;
3694          if (symbol == 338) /* Latin capital ligature oe */
3695             symbol = 140;
3696 
3697          /* Find glyph LUT data */
3698          if (symbol <= font_eng->glyph_max)
3699             symbol_lut = font_eng->lut[symbol];
3700          else if ((symbol >= font_rus->glyph_min) && (symbol <= font_rus->glyph_max))
3701             symbol_lut = font_rus->lut[symbol - font_rus->glyph_min];
3702          else
3703             continue;
3704 
3705          for (j = 0; j < FONT_10X10_HEIGHT; j++)
3706          {
3707             unsigned buff_offset = ((y + j) * fb_width) + x;
3708 
3709             for (i = 0; i < FONT_10X10_WIDTH; i++)
3710             {
3711                if (*(symbol_lut + i + (j * FONT_10X10_WIDTH)))
3712                {
3713                   uint16_t *frame_buf_ptr = frame_buf_data + buff_offset + i;
3714 
3715                   /* Text pixel + right shadow */
3716                   memcpy(frame_buf_ptr, color_buf, sizeof(color_buf));
3717 
3718                   /* Bottom shadow */
3719                   frame_buf_ptr += fb_width;
3720                   memcpy(frame_buf_ptr, shadow_color_buf, sizeof(shadow_color_buf));
3721                }
3722             }
3723          }
3724       }
3725 
3726       x += FONT_10X10_WIDTH_STRIDE;
3727    }
3728 }
3729 #endif
3730 
3731 static void (*blit_line)(rgui_t *rgui, unsigned fb_width, int x, int y,
3732       const char *message, uint16_t color, uint16_t shadow_color) = blit_line_regular;
3733 
3734 /* blit_symbol() */
3735 
rgui_get_symbol_data(enum rgui_symbol_type symbol)3736 static const uint8_t *rgui_get_symbol_data(enum rgui_symbol_type symbol)
3737 {
3738    switch (symbol)
3739    {
3740       case RGUI_SYMBOL_BACKSPACE:
3741          return rgui_symbol_data_backspace;
3742       case RGUI_SYMBOL_ENTER:
3743          return rgui_symbol_data_enter;
3744       case RGUI_SYMBOL_SHIFT_UP:
3745          return rgui_symbol_data_shift_up;
3746       case RGUI_SYMBOL_SHIFT_DOWN:
3747          return rgui_symbol_data_shift_down;
3748       case RGUI_SYMBOL_NEXT:
3749          return rgui_symbol_data_next;
3750       case RGUI_SYMBOL_TEXT_CURSOR:
3751          return rgui_symbol_data_text_cursor;
3752       case RGUI_SYMBOL_CHARGING:
3753          return rgui_symbol_data_charging;
3754       case RGUI_SYMBOL_BATTERY_100:
3755          return rgui_symbol_data_battery_100;
3756       case RGUI_SYMBOL_BATTERY_80:
3757          return rgui_symbol_data_battery_80;
3758       case RGUI_SYMBOL_BATTERY_60:
3759          return rgui_symbol_data_battery_60;
3760       case RGUI_SYMBOL_BATTERY_40:
3761          return rgui_symbol_data_battery_40;
3762       case RGUI_SYMBOL_BATTERY_20:
3763          return rgui_symbol_data_battery_20;
3764       case RGUI_SYMBOL_CHECKMARK:
3765          return rgui_symbol_data_checkmark;
3766       case RGUI_SYMBOL_SWITCH_ON_LEFT:
3767          return rgui_symbol_data_switch_on_left;
3768       case RGUI_SYMBOL_SWITCH_ON_CENTRE:
3769          return rgui_symbol_data_switch_on_centre;
3770       case RGUI_SYMBOL_SWITCH_ON_RIGHT:
3771          return rgui_symbol_data_switch_on_right;
3772       case RGUI_SYMBOL_SWITCH_OFF_LEFT:
3773          return rgui_symbol_data_switch_off_left;
3774       case RGUI_SYMBOL_SWITCH_OFF_CENTRE:
3775          return rgui_symbol_data_switch_off_centre;
3776       case RGUI_SYMBOL_SWITCH_OFF_RIGHT:
3777          return rgui_symbol_data_switch_off_right;
3778       default:
3779          break;
3780    }
3781 
3782    return NULL;
3783 }
3784 
blit_symbol_regular(rgui_t * rgui,unsigned fb_width,int x,int y,enum rgui_symbol_type symbol,uint16_t color,uint16_t shadow_color)3785 static void blit_symbol_regular(rgui_t *rgui, unsigned fb_width, int x, int y,
3786       enum rgui_symbol_type symbol, uint16_t color, uint16_t shadow_color)
3787 {
3788    unsigned i, j;
3789    uint16_t *frame_buf_data   = rgui->frame_buf.data;
3790    const uint8_t *symbol_data = rgui_get_symbol_data(symbol);
3791 
3792    if (!symbol_data)
3793       return;
3794 
3795    for (j = 0; j < RGUI_SYMBOL_HEIGHT; j++)
3796    {
3797       unsigned buff_offset = ((y + j) * fb_width) + x;
3798 
3799       for (i = 0; i < RGUI_SYMBOL_WIDTH; i++)
3800       {
3801          if (*symbol_data++ == 1)
3802             *(frame_buf_data + buff_offset + i) = color;
3803       }
3804    }
3805 }
3806 
blit_symbol_shadow(rgui_t * rgui,unsigned fb_width,int x,int y,enum rgui_symbol_type symbol,uint16_t color,uint16_t shadow_color)3807 static void blit_symbol_shadow(rgui_t *rgui, unsigned fb_width, int x, int y,
3808       enum rgui_symbol_type symbol, uint16_t color, uint16_t shadow_color)
3809 {
3810    unsigned i, j;
3811    uint16_t *frame_buf_data   = rgui->frame_buf.data;
3812    const uint8_t *symbol_data = rgui_get_symbol_data(symbol);
3813    uint16_t color_buf[2];
3814    uint16_t shadow_color_buf[2];
3815 
3816    color_buf[0] = color;
3817    color_buf[1] = shadow_color;
3818 
3819    shadow_color_buf[0] = shadow_color;
3820    shadow_color_buf[1] = shadow_color;
3821 
3822    if (!symbol_data)
3823       return;
3824 
3825    for (j = 0; j < RGUI_SYMBOL_HEIGHT; j++)
3826    {
3827       unsigned buff_offset = ((y + j) * fb_width) + x;
3828 
3829       for (i = 0; i < RGUI_SYMBOL_WIDTH; i++)
3830       {
3831          if (*symbol_data++ == 1)
3832          {
3833             uint16_t *frame_buf_ptr = frame_buf_data + buff_offset + i;
3834 
3835             /* Symbol pixel + right shadow */
3836             memcpy(frame_buf_ptr, color_buf, sizeof(color_buf));
3837 
3838             /* Bottom shadow */
3839             frame_buf_ptr += fb_width;
3840             memcpy(frame_buf_ptr, shadow_color_buf, sizeof(shadow_color_buf));
3841          }
3842       }
3843    }
3844 }
3845 
3846 static void (*blit_symbol)(rgui_t *rgui, unsigned fb_width, int x, int y,
3847       enum rgui_symbol_type symbol, uint16_t color, uint16_t shadow_color) = blit_symbol_regular;
3848 
rgui_set_blit_functions(unsigned language,bool draw_shadow,bool extended_ascii)3849 static void rgui_set_blit_functions(unsigned language,
3850       bool draw_shadow, bool extended_ascii)
3851 {
3852    if (draw_shadow)
3853    {
3854 #ifdef HAVE_LANGEXTRA
3855       switch (language)
3856       {
3857          case RETRO_LANGUAGE_JAPANESE:
3858          case RETRO_LANGUAGE_KOREAN:
3859          case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
3860          case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
3861             blit_line = blit_line_cjk_shadow;
3862             break;
3863          case RETRO_LANGUAGE_RUSSIAN:
3864             blit_line = blit_line_rus_shadow;
3865             break;
3866          default:
3867             if (extended_ascii)
3868                blit_line = blit_line_extended_shadow;
3869             else
3870                blit_line = blit_line_regular_shadow;
3871             break;
3872       }
3873 #else
3874       if (extended_ascii)
3875          blit_line = blit_line_extended_shadow;
3876       else
3877          blit_line = blit_line_regular_shadow;
3878 #endif
3879 
3880       blit_symbol = blit_symbol_shadow;
3881    }
3882    else
3883    {
3884 #ifdef HAVE_LANGEXTRA
3885       switch (language)
3886       {
3887          case RETRO_LANGUAGE_JAPANESE:
3888          case RETRO_LANGUAGE_KOREAN:
3889          case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
3890          case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
3891             blit_line = blit_line_cjk;
3892             break;
3893          case RETRO_LANGUAGE_RUSSIAN:
3894             blit_line = blit_line_rus;
3895             break;
3896          default:
3897             if (extended_ascii)
3898                blit_line = blit_line_extended;
3899             else
3900                blit_line = blit_line_regular;
3901             break;
3902       }
3903 #else
3904       if (extended_ascii)
3905          blit_line = blit_line_extended;
3906       else
3907          blit_line = blit_line_regular;
3908 #endif
3909 
3910       blit_symbol = blit_symbol_regular;
3911    }
3912 }
3913 
3914 /* ==============================
3915  * blit_line/symbol() END
3916  * ============================== */
3917 
rgui_set_message(void * data,const char * message)3918 static void rgui_set_message(void *data, const char *message)
3919 {
3920    rgui_t           *rgui = (rgui_t*)data;
3921 
3922    if (!rgui || !message)
3923       return;
3924 
3925    rgui->msgbox[0] = '\0';
3926 
3927    if (!string_is_empty(message))
3928       strlcpy(rgui->msgbox, message, sizeof(rgui->msgbox));
3929 
3930    rgui->force_redraw = true;
3931 }
3932 
rgui_render_messagebox(rgui_t * rgui,const char * message,unsigned fb_width,unsigned fb_height)3933 static void rgui_render_messagebox(rgui_t *rgui, const char *message,
3934       unsigned fb_width, unsigned fb_height)
3935 {
3936    int x, y;
3937    size_t i;
3938    unsigned width           = 0;
3939    unsigned glyphs_width    = 0;
3940    unsigned height          = 0;
3941    struct string_list list  = {0};
3942    uint16_t *frame_buf_data = rgui->frame_buf.data;
3943    char wrapped_message[MENU_SUBLABEL_MAX_LENGTH];
3944 
3945    wrapped_message[0] = '\0';
3946 
3947    if (string_is_empty(message))
3948       return;
3949 
3950    /* Split message into lines */
3951    word_wrap(
3952          wrapped_message, sizeof(wrapped_message), message,
3953          rgui->term_layout.width,
3954          100, 0);
3955 
3956    string_list_initialize(&list);
3957    if (     !string_split_noalloc(&list, wrapped_message, "\n")
3958          || list.elems == 0)
3959    {
3960       string_list_deinitialize(&list);
3961       return;
3962    }
3963 
3964    for (i = 0; i < list.size; i++)
3965    {
3966       unsigned line_width;
3967       char     *msg   = list.elems[i].data;
3968       unsigned msglen = (unsigned)utf8len(msg);
3969 
3970       line_width   = msglen * rgui->font_width_stride - 1 + 6 + 10;
3971       width        = MAX(width, line_width);
3972       glyphs_width = MAX(glyphs_width, msglen);
3973    }
3974 
3975    height = (unsigned)(rgui->font_height_stride * list.size + 6 + 10);
3976    x      = ((int)fb_width  - (int)width) / 2;
3977    y      = ((int)fb_height - (int)height) / 2;
3978 
3979    height = (height > fb_height) ? fb_height : height;
3980    x      = (x < 0)              ? 0 : x;
3981    y      = (y < 0)              ? 0 : y;
3982 
3983    if (frame_buf_data)
3984    {
3985       uint16_t border_dark_color  = rgui->colors.border_dark_color;
3986       uint16_t border_light_color = rgui->colors.border_light_color;
3987       bool border_thickness       = rgui->border_thickness;
3988 
3989       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
3990             x + 5, y + 5, width - 10, height - 10,
3991             rgui->colors.bg_dark_color, rgui->colors.bg_light_color, rgui->bg_thickness);
3992 
3993       /* Note: We draw borders around message boxes regardless
3994        * of the rgui->border_enable setting, because they look
3995        * ridiculous without... */
3996 
3997       /* Draw drop shadow, if required */
3998       if (rgui->shadow_enable)
3999       {
4000          uint16_t shadow_color = rgui->colors.shadow_color;
4001 
4002          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4003                x + 5, y + 5, 1, height - 5, shadow_color);
4004          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4005                x + 5, y + 5, width - 5, 1, shadow_color);
4006          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4007                x + width, y + 1, 1, height, shadow_color);
4008          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4009                x + 1, y + height, width, 1, shadow_color);
4010       }
4011 
4012       /* Draw border */
4013       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4014             x, y, width - 5, 5,
4015             border_dark_color, border_light_color, border_thickness);
4016       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4017             x + width - 5, y, 5, height - 5,
4018             border_dark_color, border_light_color, border_thickness);
4019       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4020             x + 5, y + height - 5, width - 5, 5,
4021             border_dark_color, border_light_color, border_thickness);
4022       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4023             x, y + 5, 5, height - 5,
4024             border_dark_color, border_light_color, border_thickness);
4025 
4026       /* Draw text */
4027       for (i = 0; i < list.size; i++)
4028       {
4029          const char *msg = list.elems[i].data;
4030          int offset_x    = (int)(rgui->font_width_stride * (glyphs_width - utf8len(msg)) / 2);
4031          int offset_y    = (int)(rgui->font_height_stride * i);
4032          int text_x      = x + 8 + offset_x;
4033          int text_y      = y + 8 + offset_y;
4034 
4035          /* Ensure we remain within the bounds of the
4036           * framebuffer */
4037          if (text_y > (int)fb_height - 10 - (int)rgui->font_height_stride)
4038             break;
4039 
4040          blit_line(rgui, fb_width, text_x, text_y, msg,
4041                rgui->colors.normal_color, rgui->colors.shadow_color);
4042       }
4043    }
4044 
4045    string_list_deinitialize(&list);
4046 }
4047 
rgui_osk_ptr_at_pos(void * data,int x,int y,unsigned width,unsigned height)4048 static int rgui_osk_ptr_at_pos(void *data, int x, int y,
4049       unsigned width, unsigned height)
4050 {
4051    size_t key_index;
4052    unsigned osk_x, osk_y;
4053    unsigned key_width;
4054    unsigned key_height;
4055    unsigned ptr_width;
4056    unsigned ptr_height;
4057    unsigned keyboard_width;
4058    unsigned keyboard_height;
4059    unsigned keyboard_offset_y;
4060    unsigned osk_width;
4061    unsigned osk_height;
4062    unsigned fb_width, fb_height;
4063    unsigned key_text_offset_x  = 8;
4064    unsigned key_text_offset_y  = 6;
4065    unsigned ptr_offset_x       = 2;
4066    unsigned ptr_offset_y       = 2;
4067    unsigned keyboard_offset_x  = 10;
4068    /* This is a lazy copy/paste from rgui_render_osk(),
4069     * but it will do for now... */
4070    rgui_t *rgui                = (rgui_t*)data;
4071    gfx_display_t *p_disp       = NULL;
4072 
4073    if (!rgui)
4074       return -1;
4075    p_disp                      = disp_get_ptr();
4076 
4077    key_width                   = rgui->font_width  + (key_text_offset_x * 2);
4078    key_height                  = rgui->font_height + (key_text_offset_y * 2);
4079    ptr_width                   = key_width  - (ptr_offset_x * 2);
4080    ptr_height                  = key_height - (ptr_offset_y * 2);
4081    keyboard_width              = key_width  * OSK_CHARS_PER_LINE;
4082    keyboard_height             = key_height * 4;
4083    keyboard_offset_y           = 10 + 15 + (2 * rgui->font_height_stride);
4084    osk_width                   = keyboard_width + 20;
4085    osk_height                  = keyboard_offset_y + keyboard_height + 10;
4086 
4087    /* Get dimensions/layout */
4088    fb_width                    = p_disp->framebuf_width;
4089    fb_height                   = p_disp->framebuf_height;
4090 
4091    osk_x                  = (fb_width  - osk_width)  / 2;
4092    osk_y                  = (fb_height - osk_height) / 2;
4093 
4094    for (key_index = 0; key_index < 44; key_index++)
4095    {
4096       unsigned key_row     = (unsigned)(key_index / OSK_CHARS_PER_LINE);
4097       unsigned key_column  = (unsigned)(key_index - (key_row * OSK_CHARS_PER_LINE));
4098 
4099       unsigned osk_ptr_x   = osk_x + keyboard_offset_x + ptr_offset_x + (key_column * key_width);
4100       unsigned osk_ptr_y   = osk_y + keyboard_offset_y + ptr_offset_y + (key_row    * key_height);
4101 
4102       if (x > osk_ptr_x && x < osk_ptr_x + ptr_width &&
4103           y > osk_ptr_y && y < osk_ptr_y + ptr_height)
4104          return (int)key_index;
4105    }
4106 
4107    return -1;
4108 }
4109 
rgui_render_osk(rgui_t * rgui,gfx_animation_ctx_ticker_t * ticker,gfx_animation_ctx_ticker_smooth_t * ticker_smooth,bool use_smooth_ticker,unsigned fb_width,unsigned fb_height)4110 static void rgui_render_osk(
4111       rgui_t *rgui,
4112       gfx_animation_ctx_ticker_t *ticker,
4113       gfx_animation_ctx_ticker_smooth_t *ticker_smooth,
4114       bool use_smooth_ticker,
4115       unsigned fb_width, unsigned fb_height)
4116 {
4117    size_t key_index;
4118 
4119    unsigned input_label_max_length;
4120    unsigned input_str_max_length;
4121    unsigned input_offset_x, input_offset_y;
4122 
4123    unsigned key_width, key_height;
4124    unsigned key_text_offset_x, key_text_offset_y;
4125    unsigned ptr_width, ptr_height;
4126    unsigned ptr_offset_x, ptr_offset_y;
4127 
4128    unsigned keyboard_width, keyboard_height;
4129    unsigned keyboard_offset_x, keyboard_offset_y;
4130 
4131    unsigned osk_width, osk_height;
4132    unsigned osk_x, osk_y;
4133 
4134    int osk_ptr              = input_event_get_osk_ptr();
4135    char **osk_grid          = input_event_get_osk_grid();
4136    const char *input_str    = menu_input_dialog_get_buffer();
4137    const char *input_label  = menu_input_dialog_get_label_buffer();
4138 
4139    uint16_t *frame_buf_data = rgui->frame_buf.data;
4140 
4141    /* Sanity check 1 */
4142    if (!frame_buf_data || osk_ptr < 0 || osk_ptr >= 44 || !osk_grid[0])
4143       return;
4144 
4145    key_text_offset_x      = 8;
4146    key_text_offset_y      = 6;
4147    key_width              = rgui->font_width  + (key_text_offset_x * 2);
4148    key_height             = rgui->font_height + (key_text_offset_y * 2);
4149    ptr_offset_x           = 2;
4150    ptr_offset_y           = 2;
4151    ptr_width              = key_width  - (ptr_offset_x * 2);
4152    ptr_height             = key_height - (ptr_offset_y * 2);
4153    keyboard_width         = key_width  * OSK_CHARS_PER_LINE;
4154    keyboard_height        = key_height * 4;
4155    keyboard_offset_x      = 10;
4156    keyboard_offset_y      = 10 + 15 + (2 * rgui->font_height_stride);
4157    input_label_max_length = (keyboard_width / rgui->font_width_stride);
4158    input_str_max_length   = input_label_max_length - 1;
4159    input_offset_x         = 10 + (keyboard_width - (input_label_max_length * rgui->font_width_stride)) / 2;
4160    input_offset_y         = 10;
4161    osk_width              = keyboard_width + 20;
4162    osk_height             = keyboard_offset_y + keyboard_height + 10;
4163    osk_x                  = (fb_width - osk_width) / 2;
4164    osk_y                  = (fb_height - osk_height) / 2;
4165 
4166    /* Sanity check 2 */
4167    if ((osk_width + 2 > fb_width) || (osk_height + 2 > fb_height))
4168    {
4169       /* This can never happen, but have to make sure...
4170        * If OSK cannot physically fit on the screen,
4171        * fallback to old style 'message box' implementation */
4172       char msg[255];
4173       msg[0] = '\0';
4174 
4175       snprintf(msg, sizeof(msg), "%s\n%s", input_label, input_str);
4176       rgui_render_messagebox(rgui, msg, fb_width, fb_height);
4177 
4178       return;
4179    }
4180 
4181    /* Draw background */
4182    rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4183          osk_x + 5, osk_y + 5, osk_width - 10, osk_height - 10,
4184          rgui->colors.bg_dark_color, rgui->colors.bg_light_color, rgui->bg_thickness);
4185 
4186    /* Draw border */
4187    if (rgui->border_enable)
4188    {
4189       uint16_t border_dark_color  = rgui->colors.border_dark_color;
4190       uint16_t border_light_color = rgui->colors.border_light_color;
4191       bool border_thickness       = rgui->border_thickness;
4192 
4193       /* Draw drop shadow, if required */
4194       if (rgui->shadow_enable)
4195       {
4196          uint16_t shadow_color = rgui->colors.shadow_color;
4197 
4198          /* Frame */
4199          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4200                osk_x + 5, osk_y + 5, osk_width - 10, 1, shadow_color);
4201          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4202                osk_x + osk_width, osk_y + 1, 1, osk_height, shadow_color);
4203          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4204                osk_x + 1, osk_y + osk_height, osk_width, 1, shadow_color);
4205          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4206                osk_x + 5, osk_y + 5, 1, osk_height - 10, shadow_color);
4207          /* Divider */
4208          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4209                osk_x + 5, osk_y + keyboard_offset_y - 5, osk_width - 10, 1, shadow_color);
4210       }
4211 
4212       /* Frame */
4213       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4214             osk_x, osk_y, osk_width - 5, 5,
4215             border_dark_color, border_light_color, border_thickness);
4216       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4217             osk_x + osk_width - 5, osk_y, 5, osk_height - 5,
4218             border_dark_color, border_light_color, border_thickness);
4219       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4220             osk_x + 5, osk_y + osk_height - 5, osk_width - 5, 5,
4221             border_dark_color, border_light_color, border_thickness);
4222       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4223             osk_x, osk_y + 5, 5, osk_height - 5,
4224             border_dark_color, border_light_color, border_thickness);
4225       /* Divider */
4226       rgui_fill_rect(frame_buf_data, fb_width, fb_height,
4227             osk_x + 5, osk_y + keyboard_offset_y - 10, osk_width - 10, 5,
4228             border_dark_color, border_light_color, border_thickness);
4229    }
4230 
4231    /* Draw input label text */
4232    if (!string_is_empty(input_label))
4233    {
4234       char input_label_buf[255];
4235       unsigned input_label_length;
4236       int input_label_x, input_label_y;
4237       unsigned ticker_x_offset = 0;
4238 
4239       input_label_buf[0] = '\0';
4240 
4241       if (use_smooth_ticker)
4242       {
4243          ticker_smooth->selected    = true;
4244          ticker_smooth->field_width = input_label_max_length * rgui->font_width_stride;
4245          ticker_smooth->src_str     = input_label;
4246          ticker_smooth->dst_str     = input_label_buf;
4247          ticker_smooth->dst_str_len = sizeof(input_label_buf);
4248          ticker_smooth->x_offset    = &ticker_x_offset;
4249 
4250          gfx_animation_ticker_smooth(ticker_smooth);
4251       }
4252       else
4253       {
4254          ticker->s        = input_label_buf;
4255          ticker->len      = input_label_max_length;
4256          ticker->str      = input_label;
4257          ticker->selected = true;
4258 
4259          gfx_animation_ticker(ticker);
4260       }
4261 
4262       input_label_length = (unsigned)(utf8len(input_label_buf) * rgui->font_width_stride);
4263       input_label_x      = ticker_x_offset + osk_x + input_offset_x + ((input_label_max_length * rgui->font_width_stride) - input_label_length) / 2;
4264       input_label_y      = osk_y + input_offset_y;
4265 
4266       blit_line(rgui, fb_width, input_label_x, input_label_y, input_label_buf,
4267             rgui->colors.normal_color, rgui->colors.shadow_color);
4268    }
4269 
4270    /* Draw input buffer text */
4271    {
4272       unsigned input_str_char_offset;
4273       int input_str_x, input_str_y;
4274       int text_cursor_x;
4275       unsigned input_str_length     = (unsigned)utf8len(input_str);
4276       const char *input_str_visible = NULL;
4277 
4278       if (input_str_length > input_str_max_length)
4279       {
4280          input_str_char_offset = input_str_length - input_str_max_length;
4281          input_str_length      = input_str_max_length;
4282       }
4283       else
4284          input_str_char_offset = 0;
4285 
4286       input_str_x = osk_x + input_offset_x;
4287       input_str_y = osk_y + input_offset_y + rgui->font_height_stride;
4288 
4289       input_str_visible = utf8skip(input_str, input_str_char_offset);
4290 
4291       if (!string_is_empty(input_str_visible))
4292          blit_line(rgui, fb_width, input_str_x, input_str_y, input_str_visible,
4293                rgui->colors.hover_color, rgui->colors.shadow_color);
4294 
4295       /* Draw text cursor */
4296       text_cursor_x = osk_x + input_offset_x + (input_str_length * rgui->font_width_stride);
4297 
4298       blit_symbol(rgui, fb_width, text_cursor_x, input_str_y, RGUI_SYMBOL_TEXT_CURSOR,
4299             rgui->colors.normal_color, rgui->colors.shadow_color);
4300    }
4301 
4302    /* Draw keyboard 'keys' */
4303    for (key_index = 0; key_index < 44; key_index++)
4304    {
4305       unsigned key_row     = (unsigned)(key_index / OSK_CHARS_PER_LINE);
4306       unsigned key_column  = (unsigned)(key_index - (key_row * OSK_CHARS_PER_LINE));
4307 
4308       int key_text_x       = osk_x + keyboard_offset_x + key_text_offset_x + (key_column * key_width);
4309       int key_text_y       = osk_y + keyboard_offset_y + key_text_offset_y + (key_row    * key_height);
4310 
4311       const char *key_text = osk_grid[key_index];
4312 
4313       /* 'Command' keys use custom symbols - have to
4314        * detect them and use blit_symbol(). Everything
4315        * else is plain text, and can be drawn directly
4316        * using blit_line(). */
4317 #ifdef HAVE_LANGEXTRA
4318       if (     string_is_equal(key_text, "\xe2\x87\xa6")) /* backspace character */
4319          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_BACKSPACE,
4320                rgui->colors.normal_color, rgui->colors.shadow_color);
4321       else if (string_is_equal(key_text, "\xe2\x8f\x8e")) /* return character */
4322          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_ENTER,
4323                rgui->colors.normal_color, rgui->colors.shadow_color);
4324       else if (string_is_equal(key_text, "\xe2\x87\xa7")) /* up arrow */
4325          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_SHIFT_UP,
4326                rgui->colors.normal_color, rgui->colors.shadow_color);
4327       else if (string_is_equal(key_text, "\xe2\x87\xa9")) /* down arrow */
4328          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_SHIFT_DOWN,
4329                rgui->colors.normal_color, rgui->colors.shadow_color);
4330       else if (string_is_equal(key_text, "\xe2\x8a\x95")) /* plus sign (next button) */
4331          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_NEXT,
4332                rgui->colors.normal_color, rgui->colors.shadow_color);
4333 #else
4334       if (     string_is_equal(key_text, "Bksp"))
4335          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_BACKSPACE,
4336                rgui->colors.normal_color, rgui->colors.shadow_color);
4337       else if (string_is_equal(key_text, "Enter"))
4338          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_ENTER,
4339                rgui->colors.normal_color, rgui->colors.shadow_color);
4340       else if (string_is_equal(key_text, "Upper"))
4341          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_SHIFT_UP,
4342                rgui->colors.normal_color, rgui->colors.shadow_color);
4343       else if (string_is_equal(key_text, "Lower"))
4344          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_SHIFT_DOWN,
4345                rgui->colors.normal_color, rgui->colors.shadow_color);
4346       else if (string_is_equal(key_text, "Next"))
4347          blit_symbol(rgui, fb_width, key_text_x, key_text_y, RGUI_SYMBOL_NEXT,
4348                rgui->colors.normal_color, rgui->colors.shadow_color);
4349 #endif
4350       else
4351          blit_line(rgui, fb_width, key_text_x, key_text_y, key_text,
4352                rgui->colors.normal_color, rgui->colors.shadow_color);
4353 
4354       /* Draw selection pointer */
4355       if (key_index == osk_ptr)
4356       {
4357          unsigned osk_ptr_x = osk_x + keyboard_offset_x + ptr_offset_x + (key_column * key_width);
4358          unsigned osk_ptr_y = osk_y + keyboard_offset_y + ptr_offset_y + (key_row    * key_height);
4359 
4360          /* Draw drop shadow, if required */
4361          if (rgui->shadow_enable)
4362          {
4363             rgui_color_rect(frame_buf_data, fb_width, fb_height,
4364                   osk_ptr_x + 1, osk_ptr_y + 1, 1, ptr_height, rgui->colors.shadow_color);
4365             rgui_color_rect(frame_buf_data, fb_width, fb_height,
4366                   osk_ptr_x + 1, osk_ptr_y + 1, ptr_width, 1, rgui->colors.shadow_color);
4367             rgui_color_rect(frame_buf_data, fb_width, fb_height,
4368                   osk_ptr_x + ptr_width, osk_ptr_y + 1, 1, ptr_height, rgui->colors.shadow_color);
4369             rgui_color_rect(frame_buf_data, fb_width, fb_height,
4370                   osk_ptr_x + 1, osk_ptr_y + ptr_height, ptr_width, 1, rgui->colors.shadow_color);
4371          }
4372 
4373          /* Draw selection rectangle */
4374          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4375                osk_ptr_x, osk_ptr_y, 1, ptr_height, rgui->colors.hover_color);
4376          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4377                osk_ptr_x, osk_ptr_y, ptr_width, 1, rgui->colors.hover_color);
4378          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4379                osk_ptr_x + ptr_width - 1, osk_ptr_y, 1, ptr_height, rgui->colors.hover_color);
4380          rgui_color_rect(frame_buf_data, fb_width, fb_height,
4381                osk_ptr_x, osk_ptr_y + ptr_height - 1, ptr_width, 1, rgui->colors.hover_color);
4382       }
4383    }
4384 }
4385 
rgui_render_toggle_switch(rgui_t * rgui,unsigned fb_width,int x,int y,bool on,uint16_t color,uint16_t shadow_color)4386 static void rgui_render_toggle_switch(rgui_t *rgui, unsigned fb_width, int x, int y,
4387       bool on, uint16_t color, uint16_t shadow_color)
4388 {
4389    int x_current = x;
4390 
4391    /* Toggle switch is just 3 adjacent symbols
4392     * > Note that we indent the left/right symbols
4393     *   by 1 pixel, to avoid the gap that is normally
4394     *   present between symbols/characters */
4395    blit_symbol(rgui, fb_width, x_current + 1, y,
4396          on ? RGUI_SYMBOL_SWITCH_ON_LEFT : RGUI_SYMBOL_SWITCH_OFF_LEFT,
4397          color, shadow_color);
4398    x_current += RGUI_SYMBOL_WIDTH_STRIDE;
4399 
4400    blit_symbol(rgui, fb_width, x_current, y,
4401          on ? RGUI_SYMBOL_SWITCH_ON_CENTRE : RGUI_SYMBOL_SWITCH_OFF_CENTRE,
4402          color, shadow_color);
4403    x_current += RGUI_SYMBOL_WIDTH_STRIDE;
4404 
4405    blit_symbol(rgui, fb_width, x_current - 1, y,
4406          on ? RGUI_SYMBOL_SWITCH_ON_RIGHT : RGUI_SYMBOL_SWITCH_OFF_RIGHT,
4407          color, shadow_color);
4408 }
4409 
rgui_get_entry_value_type(const char * entry_value,bool entry_checked,bool switch_icons_enabled)4410 static enum rgui_entry_value_type rgui_get_entry_value_type(
4411       const char *entry_value, bool entry_checked,
4412       bool switch_icons_enabled)
4413 {
4414    enum rgui_entry_value_type value_type = RGUI_ENTRY_VALUE_NONE;
4415 
4416    if (!string_is_empty(entry_value))
4417    {
4418       value_type = RGUI_ENTRY_VALUE_TEXT;
4419 
4420       if (switch_icons_enabled)
4421       {
4422          /* Toggle switch off */
4423          if (string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED)) ||
4424              string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF)))
4425             value_type = RGUI_ENTRY_VALUE_SWITCH_OFF;
4426          /* Toggle switch on */
4427          else if (string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED)) ||
4428                   string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON)))
4429             value_type = RGUI_ENTRY_VALUE_SWITCH_ON;
4430       }
4431    }
4432    else if (entry_checked)
4433       value_type = RGUI_ENTRY_VALUE_CHECKMARK;
4434 
4435    return value_type;
4436 }
4437 
4438 #if defined(GEKKO)
4439 /* Need to forward declare this for the Wii build
4440  * (I'm not going to reorder the functions and mess
4441  * up the git diff for a single platform...) */
4442 static bool rgui_set_aspect_ratio(rgui_t *rgui, gfx_display_t *p_disp,
4443       bool delay_update);
4444 #endif
4445 
rgui_render(void * data,unsigned width,unsigned height,bool is_idle)4446 static void rgui_render(void *data,
4447       unsigned width, unsigned height,
4448       bool is_idle)
4449 {
4450    gfx_animation_ctx_ticker_t ticker;
4451    gfx_animation_ctx_ticker_smooth_t ticker_smooth;
4452    unsigned x, y;
4453    size_t i, end, fb_pitch, old_start, new_start;
4454    unsigned fb_width, fb_height;
4455    static bool display_kb         = false;
4456    static const char* const
4457       ticker_spacer               = RGUI_TICKER_SPACER;
4458    int bottom                     = 0;
4459    unsigned ticker_x_offset       = 0;
4460    size_t entries_end             = 0;
4461    bool msg_force                 = false;
4462    bool fb_size_changed           = false;
4463    settings_t *settings           = config_get_ptr();
4464    rgui_t *rgui                   = (rgui_t*)data;
4465    enum gfx_animation_ticker_type
4466       menu_ticker_type            = (enum gfx_animation_ticker_type)
4467          settings->uints.menu_ticker_type;
4468    bool rgui_inline_thumbnails    = settings->bools.menu_rgui_inline_thumbnails;
4469    bool menu_battery_level_enable = settings->bools.menu_battery_level_enable;
4470    bool use_smooth_ticker         = settings->bools.menu_ticker_smooth;
4471    bool rgui_swap_thumbnails      = settings->bools.menu_rgui_swap_thumbnails;
4472    bool rgui_full_width_layout    = settings->bools.menu_rgui_full_width_layout;
4473    bool rgui_switch_icons         = settings->bools.menu_rgui_switch_icons;
4474    bool menu_show_sublabels       = settings->bools.menu_show_sublabels;
4475    bool video_fullscreen          = settings->bools.video_fullscreen;
4476    bool menu_mouse_enable         = settings->bools.menu_mouse_enable;
4477    bool menu_core_enable          = settings->bools.menu_core_enable;
4478    bool menu_timedate_enable      = settings->bools.menu_timedate_enable;
4479    bool current_display_cb        = false;
4480 
4481    bool show_fs_thumbnail         =
4482          rgui->show_fs_thumbnail &&
4483          rgui->entry_has_thumbnail &&
4484          (rgui->fs_thumbnail.is_valid || (rgui->thumbnail_queue_size > 0));
4485    gfx_animation_t *p_anim        = anim_get_ptr();
4486    gfx_display_t *p_disp          = disp_get_ptr();
4487 
4488    /* Sanity check */
4489    if (!rgui || !rgui->frame_buf.data)
4490       return;
4491 
4492    /* Apply pending aspect ratio update */
4493    if (rgui->aspect_update_pending)
4494    {
4495       command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
4496       rgui->aspect_update_pending = false;
4497    }
4498 
4499    /* Refresh current menu, if required */
4500    if (rgui->force_menu_refresh)
4501    {
4502       bool refresh = false;
4503       menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
4504       menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL);
4505 
4506       /* Menu entries may change as a result of the
4507        * refresh; skip rendering of the 'obsolete'
4508        * menu this frame, and force a redraw of the
4509        * updated menu on the next frame */
4510       rgui->force_redraw       = true;
4511       rgui->force_menu_refresh = false;
4512       return;
4513    }
4514 
4515    current_display_cb = menu_input_dialog_get_display_kb();
4516 
4517    if (!rgui->force_redraw)
4518    {
4519       msg_force = p_disp->msg_force;
4520 
4521       if (menu_entries_ctl(MENU_ENTRIES_CTL_NEEDS_REFRESH, NULL)
4522             && !msg_force)
4523          return;
4524 
4525       if (  !display_kb &&
4526             !current_display_cb &&
4527             (is_idle || !GFX_DISPLAY_GET_UPDATE_PENDING(p_anim, p_disp)))
4528          return;
4529    }
4530 
4531    display_kb = current_display_cb;
4532    fb_width   = p_disp->framebuf_width;
4533    fb_height  = p_disp->framebuf_height;
4534    fb_pitch   = p_disp->framebuf_pitch;
4535 
4536    /* If the framebuffer changed size, or the background config has
4537     * changed, recache the background buffer */
4538    fb_size_changed = (rgui->last_width  != fb_width) ||
4539                      (rgui->last_height != fb_height);
4540 
4541 #if defined(GEKKO)
4542    /* Wii gfx driver changes menu framebuffer size at
4543     * will... If a change is detected, all texture buffers
4544     * must be regenerated - easiest way is to just call
4545     * rgui_set_aspect_ratio() */
4546    if (fb_size_changed)
4547       rgui_set_aspect_ratio(rgui, p_disp, false);
4548 #endif
4549 
4550    if (rgui->bg_modified || fb_size_changed)
4551    {
4552       rgui_cache_background(rgui, fb_width, fb_height, fb_pitch);
4553 
4554       /* Reinitialise particle effect, if required */
4555       if (fb_size_changed &&
4556             (rgui->particle_effect != RGUI_PARTICLE_EFFECT_NONE))
4557          rgui_init_particle_effect(rgui, p_disp);
4558 
4559       rgui->last_width  = fb_width;
4560       rgui->last_height = fb_height;
4561    }
4562 
4563    if (rgui->bg_modified)
4564       rgui->bg_modified      = false;
4565 
4566    p_disp->framebuf_dirty    = true;
4567    GFX_ANIMATION_CLEAR_ACTIVE(p_anim);
4568 
4569    rgui->force_redraw        = false;
4570 
4571    entries_end               = menu_entries_get_size();
4572 
4573    /* Get offset of bottommost entry */
4574    bottom                    = (int)(entries_end - rgui->term_layout.height);
4575    menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start);
4576 
4577    if (old_start > (unsigned)bottom)
4578    {
4579       /* MENU_ENTRIES_CTL_SET_START requires a pointer of
4580        * type size_t, so have to create a copy of 'bottom'
4581        * here to avoid memory errors... */
4582       size_t bottom_cpy = (size_t)bottom;
4583       menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &bottom_cpy);
4584    }
4585 
4586    /* Handle pointer input
4587     * Note: This is ignored when showing a fullscreen thumbnail */
4588    if ((rgui->pointer.type != MENU_POINTER_DISABLED) &&
4589        rgui->pointer.active && !show_fs_thumbnail)
4590    {
4591       /* Update currently 'highlighted' item */
4592       if (rgui->pointer.y > rgui->term_layout.start_y)
4593       {
4594          unsigned new_ptr;
4595          menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start);
4596 
4597          /* Note: It's okay for this to go out of range
4598           * (limits are checked in rgui_pointer_up()) */
4599          new_ptr = (unsigned)((rgui->pointer.y - rgui->term_layout.start_y) / rgui->font_height_stride) + old_start;
4600 
4601          menu_input_set_pointer_selection(new_ptr);
4602       }
4603 
4604       /* Allow drag-scrolling if items are currently off-screen */
4605       if (rgui->pointer.dragged && (bottom > 0))
4606       {
4607          size_t start;
4608          int16_t scroll_y_max = bottom * rgui->font_height_stride;
4609 
4610          rgui->scroll_y += -1 * rgui->pointer.dy;
4611          rgui->scroll_y = (rgui->scroll_y < 0)            ? 0            : rgui->scroll_y;
4612          rgui->scroll_y = (rgui->scroll_y > scroll_y_max) ? scroll_y_max : rgui->scroll_y;
4613 
4614          start = rgui->scroll_y / rgui->font_height_stride;
4615          menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start);
4616       }
4617    }
4618 
4619    /* Start position may have changed - get current
4620     * value and determine index of last displayed entry */
4621    menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &old_start);
4622    end = ((old_start + rgui->term_layout.height) <= entries_end) ?
4623          old_start + rgui->term_layout.height : entries_end;
4624 
4625    /* Do not scroll if all items are visible. */
4626    if (entries_end <= rgui->term_layout.height)
4627    {
4628       size_t start = 0;
4629       menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start);
4630    }
4631 
4632    /* Render background */
4633    rgui_render_background(rgui, fb_width, fb_height, fb_pitch);
4634 
4635    /* Render particle effect, if required */
4636    if (rgui->particle_effect != RGUI_PARTICLE_EFFECT_NONE)
4637       rgui_render_particle_effect(rgui, p_anim, fb_width, fb_height);
4638 
4639    /* If screensaver is active, skip drawing of
4640     * text/thumbnails */
4641    if (rgui->show_screensaver)
4642       return;
4643 
4644    /* We use a single ticker for all text animations,
4645     * with the following configuration: */
4646    if (use_smooth_ticker)
4647    {
4648       ticker_smooth.idx           = p_anim->ticker_pixel_idx;
4649       ticker_smooth.font          = NULL;
4650       ticker_smooth.glyph_width   = rgui->font_width_stride;
4651       ticker_smooth.type_enum     = menu_ticker_type;
4652       ticker_smooth.spacer        = ticker_spacer;
4653       ticker_smooth.dst_str_width = NULL;
4654    }
4655    else
4656    {
4657       ticker.idx                  = p_anim->ticker_idx;
4658       ticker.type_enum            = menu_ticker_type;
4659       ticker.spacer               = ticker_spacer;
4660    }
4661 
4662    /* Note: On-screen keyboard takes precedence over
4663     * normal menu thumbnail/text list display modes */
4664    if (current_display_cb)
4665       rgui_render_osk(rgui, &ticker, &ticker_smooth, use_smooth_ticker,
4666             fb_width, fb_height);
4667    else if (show_fs_thumbnail)
4668    {
4669       /* If fullscreen thumbnails are enabled and we are viewing a playlist,
4670        * switch to fullscreen thumbnail view mode if either current thumbnail
4671        * is valid or we are waiting for current thumbnail to load
4672        * (if load is pending we'll get a blank screen + title, but
4673        * this is better than switching back to the text playlist
4674        * view, which causes ugly flickering when scrolling quickly
4675        * through a list...) */
4676       const char *thumbnail_title = NULL;
4677       char thumbnail_title_buf[255];
4678       unsigned title_x, title_width;
4679       thumbnail_title_buf[0] = '\0';
4680 
4681       /* Draw thumbnail */
4682       rgui_render_fs_thumbnail(rgui, fb_width, fb_height, fb_pitch);
4683 
4684       /* Get thumbnail title */
4685       if (gfx_thumbnail_get_label(rgui->thumbnail_path_data, &thumbnail_title))
4686       {
4687          /* Format thumbnail title */
4688          if (use_smooth_ticker)
4689          {
4690             ticker_smooth.selected    = true;
4691             ticker_smooth.field_width = (rgui->term_layout.width - 10) * rgui->font_width_stride;
4692             ticker_smooth.src_str     = thumbnail_title;
4693             ticker_smooth.dst_str     = thumbnail_title_buf;
4694             ticker_smooth.dst_str_len = sizeof(thumbnail_title_buf);
4695             ticker_smooth.x_offset    = &ticker_x_offset;
4696 
4697             /* If title is scrolling, then width == field_width */
4698             if (gfx_animation_ticker_smooth(&ticker_smooth))
4699                title_width            = ticker_smooth.field_width;
4700             else
4701                title_width            = (unsigned)(utf8len(thumbnail_title_buf) * rgui->font_width_stride);
4702          }
4703          else
4704          {
4705             ticker.s        = thumbnail_title_buf;
4706             ticker.len      = rgui->term_layout.width - 10;
4707             ticker.str      = thumbnail_title;
4708             ticker.selected = true;
4709 
4710             gfx_animation_ticker(&ticker);
4711 
4712             title_width     = (unsigned)(utf8len(thumbnail_title_buf) * rgui->font_width_stride);
4713          }
4714 
4715          title_x = rgui->term_layout.start_x + ((rgui->term_layout.width * rgui->font_width_stride) - title_width) / 2;
4716 
4717          /* Draw thumbnail title background */
4718          rgui_fill_rect(rgui->frame_buf.data, fb_width, fb_height,
4719                title_x - 5, 0, title_width + 10, rgui->font_height_stride,
4720                rgui->colors.bg_dark_color, rgui->colors.bg_light_color, rgui->bg_thickness);
4721 
4722          /* Draw thumbnail title */
4723          blit_line(rgui, fb_width, ticker_x_offset + title_x,
4724                0, thumbnail_title_buf,
4725                rgui->colors.hover_color, rgui->colors.shadow_color);
4726       }
4727    }
4728    else
4729    {
4730       /* Render usual text */
4731       size_t selection               = menu_navigation_get_selection();
4732       char title_buf[255];
4733       size_t title_max_len;
4734       size_t title_len;
4735       unsigned title_x;
4736       unsigned title_y               = rgui->term_layout.start_y - rgui->font_height_stride;
4737       unsigned term_end_x            = rgui->term_layout.start_x + (rgui->term_layout.width * rgui->font_width_stride);
4738       unsigned timedate_x            = term_end_x - (5 * rgui->font_width_stride);
4739       unsigned core_name_len         = menu_timedate_enable ?
4740             ((timedate_x - rgui->term_layout.start_x) / rgui->font_width_stride) - 3 :
4741                   rgui->term_layout.width - 1;
4742       bool show_mini_thumbnails      = rgui->is_playlist && rgui_inline_thumbnails;
4743       bool show_thumbnail            = false;
4744       bool show_left_thumbnail       = false;
4745       unsigned thumbnail_panel_width = 0;
4746       unsigned term_mid_point        = 0;
4747       size_t powerstate_len          = 0;
4748 
4749       /* Cache mini thumbnail related parameters, if required */
4750       if (show_mini_thumbnails)
4751       {
4752          /* Get whether each thumbnail type is enabled */
4753          show_thumbnail = rgui->entry_has_thumbnail &&
4754                (rgui->mini_thumbnail.is_valid || (rgui->thumbnail_queue_size > 0));
4755          show_left_thumbnail = rgui->entry_has_left_thumbnail &&
4756                (rgui->mini_left_thumbnail.is_valid || (rgui->left_thumbnail_queue_size > 0));
4757 
4758          /* Get maximum width of thumbnail 'panel' on right side
4759           * of screen */
4760          thumbnail_panel_width = rgui_get_mini_thumbnail_fullwidth(rgui);
4761 
4762          if ((rgui->entry_has_thumbnail && rgui->thumbnail_queue_size > 0) ||
4763              (rgui->entry_has_left_thumbnail && rgui->left_thumbnail_queue_size > 0))
4764             thumbnail_panel_width = rgui->mini_thumbnail_max_width;
4765 
4766          /* Index (relative to first displayed menu entry) of
4767           * the vertical centre of RGUI's 'terminal'
4768           * (required to determine whether a particular entry
4769           * is adjacent to the 'right' or 'left' thumbnail) */
4770          term_mid_point = (unsigned)((rgui->term_layout.height * 0.5f) + 0.5f) - 1;
4771       }
4772 
4773       /* Show battery indicator, if required */
4774       if (menu_battery_level_enable)
4775       {
4776          gfx_display_ctx_powerstate_t powerstate;
4777          char percent_str[12];
4778 
4779          percent_str[0] = '\0';
4780 
4781          powerstate.s   = percent_str;
4782          powerstate.len = sizeof(percent_str);
4783 
4784          menu_display_powerstate(&powerstate);
4785 
4786          if (powerstate.battery_enabled)
4787          {
4788             powerstate_len = utf8len(percent_str);
4789 
4790             if (powerstate_len > 0)
4791             {
4792                unsigned powerstate_x;
4793                enum rgui_symbol_type powerstate_symbol;
4794                uint16_t powerstate_color = (powerstate.percent > RGUI_BATTERY_WARN_THRESHOLD || powerstate.charging)
4795                   ? rgui->colors.title_color
4796                   : rgui->colors.hover_color;
4797 
4798                if (powerstate.charging)
4799                   powerstate_symbol = RGUI_SYMBOL_CHARGING;
4800                else
4801                {
4802                   if (powerstate.percent > 80)
4803                      powerstate_symbol = RGUI_SYMBOL_BATTERY_100;
4804                   else if (powerstate.percent > 60)
4805                      powerstate_symbol = RGUI_SYMBOL_BATTERY_80;
4806                   else if (powerstate.percent > 40)
4807                      powerstate_symbol = RGUI_SYMBOL_BATTERY_60;
4808                   else if (powerstate.percent > 20)
4809                      powerstate_symbol = RGUI_SYMBOL_BATTERY_40;
4810                   else
4811                      powerstate_symbol = RGUI_SYMBOL_BATTERY_20;
4812                }
4813 
4814                /* Note: percent symbol is particularly hideous when
4815                 * drawn using RGUI's bitmap font, so strip it off the
4816                 * end of the output string... */
4817                percent_str[powerstate_len - 1] = '\0';
4818 
4819                powerstate_x = (unsigned)(term_end_x -
4820                      (RGUI_SYMBOL_WIDTH_STRIDE + (powerstate_len * rgui->font_width_stride)));
4821 
4822                /* Draw symbol */
4823                blit_symbol(rgui, fb_width, powerstate_x, title_y, powerstate_symbol,
4824                            powerstate_color, rgui->colors.shadow_color);
4825 
4826                /* Print text */
4827                blit_line(rgui, fb_width,
4828                      powerstate_x + RGUI_SYMBOL_WIDTH_STRIDE + rgui->font_width_stride, title_y,
4829                      percent_str, powerstate_color, rgui->colors.shadow_color);
4830 
4831                /* Final length of battery indicator is 'powerstate_len' + a
4832                 * spacer of 3 characters */
4833                powerstate_len += 3;
4834             }
4835          }
4836       }
4837 
4838       /* Print title */
4839       title_max_len = rgui->term_layout.width - 5 - (powerstate_len > 5 ? powerstate_len : 5);
4840       title_buf[0] = '\0';
4841 
4842       if (use_smooth_ticker)
4843       {
4844          ticker_smooth.selected    = true;
4845          ticker_smooth.field_width = title_max_len * rgui->font_width_stride;
4846          ticker_smooth.src_str     = rgui->menu_title;
4847          ticker_smooth.dst_str     = title_buf;
4848          ticker_smooth.dst_str_len = sizeof(title_buf);
4849          ticker_smooth.x_offset    = &ticker_x_offset;
4850 
4851          /* If title is scrolling, then title_len == title_max_len */
4852          if (gfx_animation_ticker_smooth(&ticker_smooth))
4853             title_len = title_max_len;
4854          else
4855             title_len = utf8len(title_buf);
4856       }
4857       else
4858       {
4859          ticker.s        = title_buf;
4860          ticker.len      = title_max_len;
4861          ticker.str      = rgui->menu_title;
4862          ticker.selected = true;
4863 
4864          gfx_animation_ticker(&ticker);
4865 
4866          title_len = utf8len(title_buf);
4867       }
4868 
4869       string_to_upper(title_buf);
4870 
4871       title_x = ticker_x_offset + rgui->term_layout.start_x +
4872                 (rgui->term_layout.width - title_len) * rgui->font_width_stride / 2;
4873 
4874       /* Title is always centred, unless it is long enough
4875        * to infringe upon the battery indicator, in which case
4876        * we shift it to the left */
4877       if (powerstate_len > 5)
4878          if (title_len > title_max_len - (powerstate_len - 5))
4879             title_x -= (powerstate_len - 5) * rgui->font_width_stride / 2;
4880 
4881       blit_line(rgui, fb_width, title_x, title_y,
4882             title_buf, rgui->colors.title_color, rgui->colors.shadow_color);
4883 
4884       /* Print menu entries */
4885       x = rgui->term_layout.start_x;
4886       y = rgui->term_layout.start_y;
4887 
4888       menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &new_start);
4889 
4890       for (i = new_start; i < end; i++, y += rgui->font_height_stride)
4891       {
4892          char entry_title_buf[255];
4893          char type_str_buf[255];
4894          menu_entry_t entry;
4895          const char *entry_value                     = NULL;
4896          size_t entry_title_max_len                  = 0;
4897          unsigned entry_value_len                    = 0;
4898          enum rgui_entry_value_type entry_value_type = RGUI_ENTRY_VALUE_NONE;
4899          bool entry_selected                         = (i == selection);
4900          uint16_t entry_color                        = entry_selected ?
4901                rgui->colors.hover_color : rgui->colors.normal_color;
4902 
4903          if (i > (selection + 100))
4904             continue;
4905 
4906          entry_title_buf[0] = '\0';
4907          type_str_buf[0]    = '\0';
4908 
4909          /* Get current entry */
4910          MENU_ENTRY_INIT(entry);
4911          entry.path_enabled     = false;
4912          entry.label_enabled    = false;
4913          entry.sublabel_enabled = false;
4914          menu_entry_get(&entry, 0, (unsigned)i, NULL, true);
4915 
4916          if (entry.enum_idx == MENU_ENUM_LABEL_CHEEVOS_PASSWORD)
4917             entry_value         = entry.password_value;
4918          else
4919             entry_value         = entry.value;
4920 
4921          /* Get base length of entry title field */
4922          entry_title_max_len = rgui->term_layout.width - (1 + 2);
4923 
4924          /* If showing mini thumbnails, reduce title field length accordingly */
4925          if (show_mini_thumbnails)
4926          {
4927             unsigned term_offset = rgui_swap_thumbnails
4928                ? (unsigned)(rgui->term_layout.height - (i - new_start) - 1)
4929                : (i - new_start);
4930             unsigned thumbnail_width = 0;
4931 
4932             /* Note:
4933              * - 'Right' thumbnail is drawn at the top
4934              * - 'Left' thumbnail is drawn at the bottom
4935              * ...unless thumbnail postions are swapped.
4936              * (legacy naming, unfortunately...) */
4937 
4938             /* An annoyance - cannot assume terminal will have a
4939              * standard layout (even though it always will...),
4940              * so have to check whether there are an odd or even
4941              * number of entries... */
4942             if ((rgui->term_layout.height & 1) == 0)
4943             {
4944                /* Even number of entries */
4945                if ((show_thumbnail      && (term_offset <= term_mid_point)) ||
4946                    (show_left_thumbnail && (term_offset >  term_mid_point)))
4947                   thumbnail_width = thumbnail_panel_width;
4948             }
4949             else
4950             {
4951                /* Odd number of entries (will always be the case) */
4952                if ((show_thumbnail      && (term_offset < term_mid_point)) ||
4953                    (show_left_thumbnail && (term_offset > term_mid_point)) ||
4954                    ((show_thumbnail || show_left_thumbnail) && (term_offset == term_mid_point)))
4955                   thumbnail_width = thumbnail_panel_width;
4956             }
4957 
4958             entry_title_max_len -= (thumbnail_width / rgui->font_width_stride) + 1;
4959          }
4960 
4961          /* Get 'type' of entry value component */
4962          entry_value_type = rgui_get_entry_value_type(
4963                entry_value, entry.checked, rgui_switch_icons);
4964 
4965          switch (entry_value_type)
4966          {
4967             case RGUI_ENTRY_VALUE_TEXT:
4968                /* If using full width layout, resize fields
4969                 * according to actual length of value string.
4970                 * Otherwise, use classic fixed widths 'rounded
4971                 * down' to current value_maxlen */
4972                entry_value_len = rgui_full_width_layout ?
4973                      (unsigned)utf8len(entry_value) :
4974                            entry.spacing;
4975 
4976                entry_value_len = (entry_value_len >
4977                      rgui->term_layout.value_maxlen) ?
4978                            rgui->term_layout.value_maxlen :
4979                                  entry_value_len;
4980 
4981                /* Update width of entry title field */
4982                entry_title_max_len -= entry_value_len + 2;
4983                break;
4984             case RGUI_ENTRY_VALUE_SWITCH_ON:
4985             case RGUI_ENTRY_VALUE_SWITCH_OFF:
4986                /* Switch icon is 3 characters wide
4987                 * (if using classic fixed width layout,
4988                 *  set maximum width to ensure icon is
4989                 *  aligned with left hand edge of values
4990                 *  column) */
4991                entry_value_len = rgui_full_width_layout ? 3 :
4992                      (RGUI_ENTRY_VALUE_MAXLEN > rgui->term_layout.value_maxlen) ?
4993                            rgui->term_layout.value_maxlen : RGUI_ENTRY_VALUE_MAXLEN;
4994 
4995                /* Update width of entry title field */
4996                entry_title_max_len -= entry_value_len + 2;
4997                break;
4998             default:
4999                break;
5000          }
5001 
5002          /* Format entry title string */
5003          if (use_smooth_ticker)
5004          {
5005             ticker_smooth.selected    = entry_selected;
5006             ticker_smooth.field_width = entry_title_max_len * rgui->font_width_stride;
5007             if (!string_is_empty(entry.rich_label))
5008                ticker_smooth.src_str  = entry.rich_label;
5009             else
5010                ticker_smooth.src_str  = entry.path;
5011             ticker_smooth.dst_str     = entry_title_buf;
5012             ticker_smooth.dst_str_len = sizeof(entry_title_buf);
5013             ticker_smooth.x_offset    = &ticker_x_offset;
5014 
5015             gfx_animation_ticker_smooth(&ticker_smooth);
5016          }
5017          else
5018          {
5019             ticker.s        = entry_title_buf;
5020             ticker.len      = entry_title_max_len;
5021             if (!string_is_empty(entry.rich_label))
5022                ticker.str   = entry.rich_label;
5023             else
5024                ticker.str   = entry.path;
5025             ticker.selected = entry_selected;
5026 
5027             gfx_animation_ticker(&ticker);
5028          }
5029 
5030          /* Print entry title */
5031          blit_line(rgui, fb_width,
5032                ticker_x_offset + x + (2 * rgui->font_width_stride), y,
5033                entry_title_buf,
5034                entry_color, rgui->colors.shadow_color);
5035 
5036          /* Print entry value, if required */
5037          switch (entry_value_type)
5038          {
5039             case RGUI_ENTRY_VALUE_TEXT:
5040                /* Format entry value string */
5041                if (use_smooth_ticker)
5042                {
5043                   ticker_smooth.field_width = entry_value_len * rgui->font_width_stride;
5044                   ticker_smooth.src_str     = entry_value;
5045                   ticker_smooth.dst_str     = type_str_buf;
5046                   ticker_smooth.dst_str_len = sizeof(type_str_buf);
5047                   ticker_smooth.x_offset    = &ticker_x_offset;
5048 
5049                   gfx_animation_ticker_smooth(&ticker_smooth);
5050                }
5051                else
5052                {
5053                   ticker.s        = type_str_buf;
5054                   ticker.len      = entry_value_len;
5055                   ticker.str      = entry_value;
5056 
5057                   gfx_animation_ticker(&ticker);
5058                }
5059 
5060                /* Print entry value */
5061                blit_line(rgui,
5062                      fb_width,
5063                      ticker_x_offset + term_end_x - ((entry_value_len + 1) * rgui->font_width_stride),
5064                      y,
5065                      type_str_buf,
5066                      entry_color, rgui->colors.shadow_color);
5067                break;
5068             case RGUI_ENTRY_VALUE_SWITCH_ON:
5069                rgui_render_toggle_switch(rgui, fb_width,
5070                      rgui_full_width_layout ?
5071                            (term_end_x - ((RGUI_SYMBOL_WIDTH_STRIDE * 3) + rgui->font_width_stride)) :
5072                                  (term_end_x - ((entry_value_len + 1) * rgui->font_width_stride)),
5073                      y,
5074                      true,
5075                      entry_color, rgui->colors.shadow_color);
5076                break;
5077             case RGUI_ENTRY_VALUE_SWITCH_OFF:
5078                rgui_render_toggle_switch(rgui, fb_width,
5079                      rgui_full_width_layout ?
5080                            (term_end_x - ((RGUI_SYMBOL_WIDTH_STRIDE * 3) + rgui->font_width_stride)) :
5081                                  (term_end_x - ((entry_value_len + 1) * rgui->font_width_stride)),
5082                      y,
5083                      false,
5084                      entry_color, rgui->colors.shadow_color);
5085                break;
5086             case RGUI_ENTRY_VALUE_CHECKMARK:
5087                /* Print marker for currently selected
5088                 * item in drop-down lists */
5089                blit_symbol(rgui, fb_width, x + rgui->font_width_stride, y,
5090                      RGUI_SYMBOL_CHECKMARK,
5091                      entry_color, rgui->colors.shadow_color);
5092                break;
5093             default:
5094                break;
5095          }
5096 
5097          /* Print selection marker, if required */
5098          if (entry_selected)
5099             blit_line(rgui, fb_width, x, y, ">",
5100                   entry_color, rgui->colors.shadow_color);
5101       }
5102 
5103       /* Draw mini thumbnails, if required */
5104       if (show_mini_thumbnails)
5105       {
5106          if (show_thumbnail)
5107             rgui_render_mini_thumbnail(rgui, &rgui->mini_thumbnail, GFX_THUMBNAIL_RIGHT,
5108                   fb_width, fb_height, fb_pitch);
5109 
5110          if (show_left_thumbnail)
5111             rgui_render_mini_thumbnail(rgui, &rgui->mini_left_thumbnail, GFX_THUMBNAIL_LEFT,
5112                   fb_width, fb_height, fb_pitch);
5113       }
5114 
5115       /* Print menu sublabel/core name (if required) */
5116       if (menu_show_sublabels && !string_is_empty(rgui->menu_sublabel))
5117       {
5118          char sublabel_buf[MENU_SUBLABEL_MAX_LENGTH];
5119          sublabel_buf[0] = '\0';
5120 
5121          if (use_smooth_ticker)
5122          {
5123             ticker_smooth.selected    = true;
5124             ticker_smooth.field_width = core_name_len * rgui->font_width_stride;
5125             ticker_smooth.src_str     = rgui->menu_sublabel;
5126             ticker_smooth.dst_str     = sublabel_buf;
5127             ticker_smooth.dst_str_len = sizeof(sublabel_buf);
5128             ticker_smooth.x_offset    = &ticker_x_offset;
5129 
5130             gfx_animation_ticker_smooth(&ticker_smooth);
5131          }
5132          else
5133          {
5134             ticker.s        = sublabel_buf;
5135             ticker.len      = core_name_len;
5136             ticker.str      = rgui->menu_sublabel;
5137             ticker.selected = true;
5138 
5139             gfx_animation_ticker(&ticker);
5140          }
5141 
5142          blit_line(rgui,
5143                fb_width,
5144                ticker_x_offset + rgui->term_layout.start_x + rgui->font_width_stride,
5145                (rgui->term_layout.height * rgui->font_height_stride) +
5146                rgui->term_layout.start_y + 2, sublabel_buf,
5147                rgui->colors.hover_color, rgui->colors.shadow_color);
5148       }
5149       else if (menu_core_enable)
5150       {
5151          char core_title[64];
5152          char core_title_buf[64];
5153          core_title[0] = core_title_buf[0] = '\0';
5154 
5155          menu_entries_get_core_title(core_title, sizeof(core_title));
5156 
5157          if (use_smooth_ticker)
5158          {
5159             ticker_smooth.selected    = true;
5160             ticker_smooth.field_width = core_name_len * rgui->font_width_stride;
5161             ticker_smooth.src_str     = core_title;
5162             ticker_smooth.dst_str     = core_title_buf;
5163             ticker_smooth.dst_str_len = sizeof(core_title_buf);
5164             ticker_smooth.x_offset    = &ticker_x_offset;
5165 
5166             gfx_animation_ticker_smooth(&ticker_smooth);
5167          }
5168          else
5169          {
5170             ticker.s        = core_title_buf;
5171             ticker.len      = core_name_len;
5172             ticker.str      = core_title;
5173             ticker.selected = true;
5174 
5175             gfx_animation_ticker(&ticker);
5176          }
5177 
5178          blit_line(rgui,
5179                fb_width,
5180                ticker_x_offset + rgui->term_layout.start_x + rgui->font_width_stride,
5181                (rgui->term_layout.height * rgui->font_height_stride) +
5182                rgui->term_layout.start_y + 2, core_title_buf,
5183                rgui->colors.hover_color, rgui->colors.shadow_color);
5184       }
5185 
5186       /* Print clock (if required) */
5187       if (menu_timedate_enable)
5188       {
5189          gfx_display_ctx_datetime_t datetime;
5190          char timedate[16];
5191 
5192          timedate[0]             = '\0';
5193 
5194          datetime.s              = timedate;
5195          datetime.len            = sizeof(timedate);
5196          datetime.time_mode      = MENU_TIMEDATE_STYLE_HM;
5197          datetime.date_separator = MENU_TIMEDATE_DATE_SEPARATOR_HYPHEN;
5198 
5199          menu_display_timedate(&datetime);
5200 
5201          blit_line(rgui,
5202                fb_width,
5203                timedate_x,
5204                (rgui->term_layout.height * rgui->font_height_stride) +
5205                rgui->term_layout.start_y + 2, timedate,
5206                rgui->colors.hover_color, rgui->colors.shadow_color);
5207       }
5208    }
5209 
5210    if (!string_is_empty(rgui->msgbox))
5211    {
5212       rgui_render_messagebox(rgui, rgui->msgbox, fb_width, fb_height);
5213       rgui->msgbox[0]    = '\0';
5214       rgui->force_redraw = true;
5215    }
5216 
5217    if (rgui->show_mouse)
5218    {
5219       bool cursor_visible   = video_fullscreen
5220          && menu_mouse_enable;
5221 
5222       /* Blit cursor */
5223       if (cursor_visible && rgui->frame_buf.data)
5224       {
5225          rgui_color_rect(rgui->frame_buf.data, fb_width, fb_height, rgui->pointer.x, rgui->pointer.y - 5, 1, 11, rgui->colors.normal_color);
5226          rgui_color_rect(rgui->frame_buf.data, fb_width, fb_height, rgui->pointer.x - 5, rgui->pointer.y, 11, 1, rgui->colors.normal_color);
5227       }
5228    }
5229 }
5230 
rgui_framebuffer_free(frame_buf_t * framebuffer)5231 static void rgui_framebuffer_free(frame_buf_t *framebuffer)
5232 {
5233    if (!framebuffer)
5234       return;
5235 
5236    framebuffer->width  = 0;
5237    framebuffer->height = 0;
5238 
5239    if (framebuffer->data)
5240       free(framebuffer->data);
5241    framebuffer->data   = NULL;
5242 }
5243 
rgui_thumbnail_free(thumbnail_t * thumbnail)5244 static void rgui_thumbnail_free(thumbnail_t *thumbnail)
5245 {
5246    if (!thumbnail)
5247       return;
5248 
5249    thumbnail->max_width = 0;
5250    thumbnail->max_height = 0;
5251    thumbnail->width = 0;
5252    thumbnail->height = 0;
5253    thumbnail->is_valid = false;
5254    thumbnail->path[0] = '\0';
5255 
5256    if (thumbnail->data)
5257       free(thumbnail->data);
5258    thumbnail->data = NULL;
5259 }
5260 
rgui_is_video_config_equal(rgui_video_settings_t * config_a,rgui_video_settings_t * config_b)5261 bool rgui_is_video_config_equal(
5262       rgui_video_settings_t *config_a, rgui_video_settings_t *config_b)
5263 {
5264    return (config_a->aspect_ratio_idx == config_b->aspect_ratio_idx) &&
5265           (config_a->viewport.width == config_b->viewport.width) &&
5266           (config_a->viewport.height == config_b->viewport.height) &&
5267           (config_a->viewport.x == config_b->viewport.x) &&
5268           (config_a->viewport.y == config_b->viewport.y);
5269 }
5270 
rgui_get_video_config(rgui_video_settings_t * video_settings)5271 static void rgui_get_video_config(rgui_video_settings_t *video_settings)
5272 {
5273    settings_t *settings        = config_get_ptr();
5274    /* Could use settings->video_viewport_custom directly,
5275     * but this seems to be the standard way of doing it... */
5276    video_viewport_t *custom_vp = video_viewport_get_custom();
5277 
5278    if (!settings)
5279       return;
5280 
5281    video_settings->aspect_ratio_idx = settings->uints.video_aspect_ratio_idx;
5282    video_settings->viewport.width   = custom_vp->width;
5283    video_settings->viewport.height  = custom_vp->height;
5284    video_settings->viewport.x       = custom_vp->x;
5285    video_settings->viewport.y       = custom_vp->y;
5286 }
5287 
rgui_set_video_config(rgui_t * rgui,rgui_video_settings_t * video_settings,bool delay_update)5288 static void rgui_set_video_config(rgui_t *rgui,
5289       rgui_video_settings_t *video_settings, bool delay_update)
5290 {
5291    settings_t *settings        = config_get_ptr();
5292    /* Could use settings->video_viewport_custom directly,
5293     * but this seems to be the standard way of doing it... */
5294    video_viewport_t *custom_vp = video_viewport_get_custom();
5295 
5296    if (!settings)
5297       return;
5298 
5299    settings->uints.video_aspect_ratio_idx = video_settings->aspect_ratio_idx;
5300    custom_vp->width                       = video_settings->viewport.width;
5301    custom_vp->height                      = video_settings->viewport.height;
5302    custom_vp->x                           = video_settings->viewport.x;
5303    custom_vp->y                           = video_settings->viewport.y;
5304 
5305    aspectratio_lut[ASPECT_RATIO_CUSTOM].value =
5306       (float)custom_vp->width / custom_vp->height;
5307 
5308    if (delay_update)
5309       rgui->aspect_update_pending = true;
5310    else
5311    {
5312       command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
5313       rgui->aspect_update_pending = false;
5314    }
5315 }
5316 
5317 /* Note: This function is only called when aspect ratio
5318  * lock is enabled */
rgui_update_menu_viewport(rgui_t * rgui,gfx_display_t * p_disp)5319 static void rgui_update_menu_viewport(rgui_t *rgui,
5320       gfx_display_t    *p_disp)
5321 {
5322    struct video_viewport vp;
5323    unsigned fb_width, fb_height;
5324 #if !defined(GEKKO)
5325    bool do_integer_scaling    = false;
5326    settings_t       *settings = config_get_ptr();
5327 #if defined(DINGUX)
5328    unsigned aspect_ratio_lock = RGUI_ASPECT_RATIO_LOCK_NONE;
5329 #else
5330    unsigned aspect_ratio_lock = settings ? settings->uints.menu_rgui_aspect_ratio_lock : 0;
5331 #endif
5332 
5333    if (!settings)
5334       return;
5335 #endif
5336 
5337    fb_width                   = p_disp->framebuf_width;
5338    fb_height                  = p_disp->framebuf_height;
5339 
5340    video_driver_get_viewport_info(&vp);
5341 
5342    /* Could do this once in rgui_init(), but seems cleaner to
5343     * handle all video config in one place... */
5344    rgui->menu_video_settings.aspect_ratio_idx = ASPECT_RATIO_CUSTOM;
5345 
5346    /* Determine custom viewport layout */
5347    if (fb_width > 0 && fb_height > 0 && vp.full_width > 0 && vp.full_height > 0)
5348    {
5349 #if defined(GEKKO)
5350       /* The Wii is a special case, since it uses anamorphic
5351        * widescreen. The display aspect ratio cannot therefore
5352        * be determined simply by dividing viewport width by height */
5353       float delta;
5354 #ifdef HW_RVL
5355       float device_aspect  = (CONF_GetAspectRatio() == CONF_ASPECT_4_3) ?
5356             (4.0f / 3.0f) : (16.0f / 9.0f);
5357 #else
5358       float device_aspect  = (4.0f / 3.0f);
5359 #endif
5360       float desired_aspect = (float)fb_width / (float)fb_height;
5361 
5362       if (device_aspect > desired_aspect)
5363       {
5364          delta = (desired_aspect / device_aspect - 1.0f) / 2.0f + 0.5f;
5365          rgui->menu_video_settings.viewport.width  = (unsigned)(2.0f * (float)vp.full_width * delta);
5366          rgui->menu_video_settings.viewport.height = vp.full_height;
5367       }
5368       else
5369       {
5370          delta = (device_aspect / desired_aspect - 1.0f) / 2.0f + 0.5f;
5371          rgui->menu_video_settings.viewport.height = (unsigned)(2.0 * vp.full_height * delta);
5372          rgui->menu_video_settings.viewport.width  = vp.full_width;
5373       }
5374 #else
5375       /* Check whether we need to perform integer scaling */
5376       do_integer_scaling = (aspect_ratio_lock
5377             == RGUI_ASPECT_RATIO_LOCK_INTEGER);
5378 
5379       if (do_integer_scaling)
5380       {
5381          unsigned width_scale  = (vp.full_width / fb_width);
5382          unsigned height_scale = (vp.full_height / fb_height);
5383          unsigned        scale = (width_scale <= height_scale)
5384             ? width_scale
5385             : height_scale;
5386 
5387          if (scale > 0)
5388          {
5389             rgui->menu_video_settings.viewport.width = scale * fb_width;
5390             rgui->menu_video_settings.viewport.height = scale * fb_height;
5391          }
5392          else
5393             do_integer_scaling = false;
5394       }
5395 
5396       /* Check whether menu should be stretched to
5397        * fill the screen, regardless of internal
5398        * aspect ratio */
5399       if (aspect_ratio_lock == RGUI_ASPECT_RATIO_LOCK_FILL_SCREEN)
5400       {
5401          rgui->menu_video_settings.viewport.width  = vp.full_width;
5402          rgui->menu_video_settings.viewport.height = vp.full_height;
5403       }
5404       /* Normal non-integer aspect-ratio-correct scaling */
5405       else if (!do_integer_scaling)
5406       {
5407          float display_aspect_ratio = (float)vp.full_width
5408             / (float)vp.full_height;
5409          float         aspect_ratio = (float)fb_width
5410             / (float)fb_height;
5411 
5412          if (aspect_ratio > display_aspect_ratio)
5413          {
5414             rgui->menu_video_settings.viewport.width  = vp.full_width;
5415             rgui->menu_video_settings.viewport.height = fb_height * vp.full_width / fb_width;
5416          }
5417          else
5418          {
5419             rgui->menu_video_settings.viewport.height = vp.full_height;
5420             rgui->menu_video_settings.viewport.width  = fb_width * vp.full_height / fb_height;
5421          }
5422       }
5423 #endif
5424 
5425       /* Sanity check */
5426       rgui->menu_video_settings.viewport.width =
5427          (rgui->menu_video_settings.viewport.width < 1)
5428          ? 1
5429          : rgui->menu_video_settings.viewport.width;
5430       rgui->menu_video_settings.viewport.height =
5431          (rgui->menu_video_settings.viewport.height < 1)
5432          ? 1
5433          : rgui->menu_video_settings.viewport.height;
5434    }
5435    else
5436    {
5437       rgui->menu_video_settings.viewport.width  = 1;
5438       rgui->menu_video_settings.viewport.height = 1;
5439    }
5440 
5441    rgui->menu_video_settings.viewport.x = (vp.full_width - rgui->menu_video_settings.viewport.width) / 2;
5442    rgui->menu_video_settings.viewport.y = (vp.full_height - rgui->menu_video_settings.viewport.height) / 2;
5443 }
5444 
rgui_set_aspect_ratio(rgui_t * rgui,gfx_display_t * p_disp,bool delay_update)5445 static bool rgui_set_aspect_ratio(rgui_t *rgui,
5446       gfx_display_t    *p_disp,
5447       bool delay_update)
5448 {
5449    unsigned base_term_width;
5450    unsigned mini_thumbnail_term_width;
5451 #if defined(GEKKO)
5452    /* Note: Maximum Wii frame buffer width is 424, not
5453     * the usual 426, since the last two bits of the
5454     * width value must be zero... */
5455    unsigned max_frame_buf_width = 424;
5456 #elif defined(DINGUX)
5457    /* Dingux devices use a fixed framebuffer size */
5458    unsigned max_frame_buf_width = RGUI_DINGUX_FB_WIDTH;
5459 #else
5460    struct video_viewport vp;
5461    unsigned max_frame_buf_width = RGUI_MAX_FB_WIDTH;
5462 #endif
5463 #if defined(DINGUX)
5464    unsigned aspect_ratio        = RGUI_DINGUX_ASPECT_RATIO;
5465    unsigned aspect_ratio_lock   = RGUI_ASPECT_RATIO_LOCK_NONE;
5466 #else
5467    settings_t       *settings   = config_get_ptr();
5468    unsigned aspect_ratio        = settings->uints.menu_rgui_aspect_ratio;
5469    unsigned aspect_ratio_lock   = settings->uints.menu_rgui_aspect_ratio_lock;
5470 #endif
5471 
5472    rgui_framebuffer_free(&rgui->frame_buf);
5473    rgui_framebuffer_free(&rgui->background_buf);
5474    rgui_thumbnail_free(&rgui->fs_thumbnail);
5475    rgui_thumbnail_free(&rgui->mini_thumbnail);
5476    rgui_thumbnail_free(&rgui->mini_left_thumbnail);
5477 
5478    /* Cache new aspect ratio */
5479    rgui->menu_aspect_ratio = aspect_ratio;
5480 
5481    /* Set frame buffer dimensions: */
5482 
5483    /* Frame buffer height */
5484 #if defined(GEKKO)
5485    /* Since Wii graphics driver can change frame buffer
5486     * dimensions at will, have to read currently set
5487     * values */
5488    rgui->frame_buf.height = p_disp->framebuf_height;
5489 #elif defined(DINGUX)
5490    /* Dingux devices use a fixed framebuffer size */
5491    rgui->frame_buf.height = RGUI_DINGUX_FB_HEIGHT;
5492 #else
5493    /* If window height is less than RGUI default
5494     * height of 240, allow the frame buffer to
5495     * 'shrink' to a minimum height of 192 */
5496    rgui->frame_buf.height = 240;
5497    video_driver_get_viewport_info(&vp);
5498    if (vp.full_height < rgui->frame_buf.height)
5499       rgui->frame_buf.height = (vp.full_height > RGUI_MIN_FB_HEIGHT) ?
5500             vp.full_height : RGUI_MIN_FB_HEIGHT;
5501 #endif
5502 
5503    /* Frame buffer width */
5504    switch (rgui->menu_aspect_ratio)
5505    {
5506       case RGUI_ASPECT_RATIO_16_9:
5507          if (rgui->frame_buf.height == 240)
5508             rgui->frame_buf.width = max_frame_buf_width;
5509          else
5510             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5511                   (16.0f / 9.0f) * (float)rgui->frame_buf.height);
5512          base_term_width = rgui->frame_buf.width;
5513          break;
5514       case RGUI_ASPECT_RATIO_16_9_CENTRE:
5515          if (rgui->frame_buf.height == 240)
5516          {
5517             rgui->frame_buf.width = max_frame_buf_width;
5518             base_term_width       = 320;
5519          }
5520          else
5521          {
5522             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5523                   (16.0f / 9.0f) * (float)rgui->frame_buf.height);
5524             base_term_width       = RGUI_ROUND_FB_WIDTH(
5525                   ( 4.0f / 3.0f) * (float)rgui->frame_buf.height);
5526          }
5527          break;
5528       case RGUI_ASPECT_RATIO_16_10:
5529          if (rgui->frame_buf.height == 240)
5530             rgui->frame_buf.width = 384;
5531          else
5532             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5533                   (16.0f / 10.0f) * (float)rgui->frame_buf.height);
5534          base_term_width = rgui->frame_buf.width;
5535          break;
5536       case RGUI_ASPECT_RATIO_16_10_CENTRE:
5537          if (rgui->frame_buf.height == 240)
5538          {
5539             rgui->frame_buf.width = 384;
5540             base_term_width       = 320;
5541          }
5542          else
5543          {
5544             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5545                   (16.0f / 10.0f) * (float)rgui->frame_buf.height);
5546             base_term_width      = RGUI_ROUND_FB_WIDTH(
5547                   ( 4.0f / 3.0f)  * (float)rgui->frame_buf.height);
5548          }
5549          break;
5550       case RGUI_ASPECT_RATIO_3_2:
5551          if (rgui->frame_buf.height == 240)
5552             rgui->frame_buf.width = 360;
5553          else
5554             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5555                   (3.0f / 2.0f) * (float)rgui->frame_buf.height);
5556          base_term_width = rgui->frame_buf.width;
5557          break;
5558       case RGUI_ASPECT_RATIO_3_2_CENTRE:
5559          if (rgui->frame_buf.height == 240)
5560          {
5561             rgui->frame_buf.width = 360;
5562             base_term_width       = 320;
5563          }
5564          else
5565          {
5566             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5567                   (3.0f / 2.0f) * (float)rgui->frame_buf.height);
5568             base_term_width       = RGUI_ROUND_FB_WIDTH(
5569                   ( 4.0f / 3.0f)  * (float)rgui->frame_buf.height);
5570          }
5571          break;
5572       case RGUI_ASPECT_RATIO_5_3:
5573          if (rgui->frame_buf.height == 240)
5574             rgui->frame_buf.width = 400;
5575          else
5576             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5577                   (5.0f / 3.0f) * (float)rgui->frame_buf.height);
5578          base_term_width = rgui->frame_buf.width;
5579          break;
5580       case RGUI_ASPECT_RATIO_5_3_CENTRE:
5581          if (rgui->frame_buf.height == 240)
5582          {
5583             rgui->frame_buf.width = 400;
5584             base_term_width       = 320;
5585          }
5586          else
5587          {
5588             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5589                   (5.0f / 3.0f) * (float)rgui->frame_buf.height);
5590             base_term_width       = RGUI_ROUND_FB_WIDTH(
5591                   ( 4.0f / 3.0f)  * (float)rgui->frame_buf.height);
5592          }
5593          break;
5594       default:
5595          /* 4:3 */
5596          if (rgui->frame_buf.height == 240)
5597             rgui->frame_buf.width = 320;
5598          else
5599             rgui->frame_buf.width = RGUI_ROUND_FB_WIDTH(
5600                   ( 4.0f / 3.0f)  * (float)rgui->frame_buf.height);
5601          base_term_width = rgui->frame_buf.width;
5602          break;
5603    }
5604 
5605    /* Ensure frame buffer/terminal width is sane
5606     * - Must be less than max_frame_buf_width
5607     *   (note that this is a redundant safety
5608     *   check - it can never actually happen...)
5609     * - On platforms other than Wii and dingux, must
5610     *   be less than window width but greater than
5611     *   defined minimum width */
5612    rgui->frame_buf.width = (rgui->frame_buf.width > max_frame_buf_width) ?
5613          max_frame_buf_width : rgui->frame_buf.width;
5614    base_term_width = (base_term_width > rgui->frame_buf.width) ?
5615          rgui->frame_buf.width : base_term_width;
5616 #if !(defined(GEKKO) || defined(DINGUX))
5617    if (vp.full_width < rgui->frame_buf.width)
5618    {
5619       rgui->frame_buf.width = (vp.full_width > RGUI_MIN_FB_WIDTH) ?
5620             RGUI_ROUND_FB_WIDTH(vp.full_width) : RGUI_MIN_FB_WIDTH;
5621 
5622       /* An annoyance: have to rescale the frame buffer
5623        * height and terminal width to maintain the correct
5624        * aspect ratio... */
5625       switch (rgui->menu_aspect_ratio)
5626       {
5627          case RGUI_ASPECT_RATIO_16_9:
5628             rgui->frame_buf.height = (unsigned)(
5629                   ( 9.0f / 16.0f) * (float)rgui->frame_buf.width);
5630             base_term_width = rgui->frame_buf.width;
5631             break;
5632          case RGUI_ASPECT_RATIO_16_9_CENTRE:
5633             rgui->frame_buf.height = (unsigned)(
5634                   ( 9.0f / 16.0f) * (float)rgui->frame_buf.width);
5635             base_term_width        = RGUI_ROUND_FB_WIDTH(
5636                   ( 4.0f / 3.0f)  * (float)rgui->frame_buf.height);
5637             base_term_width = (base_term_width < RGUI_MIN_FB_WIDTH) ?
5638                   RGUI_MIN_FB_WIDTH : base_term_width;
5639             break;
5640          case RGUI_ASPECT_RATIO_16_10:
5641             rgui->frame_buf.height = (unsigned)(
5642                   (10.0f / 16.0f) * (float)rgui->frame_buf.width);
5643             base_term_width = rgui->frame_buf.width;
5644             break;
5645          case RGUI_ASPECT_RATIO_16_10_CENTRE:
5646             rgui->frame_buf.height = (unsigned)(
5647                   (10.0f / 16.0f) * (float)rgui->frame_buf.width);
5648             base_term_width        = RGUI_ROUND_FB_WIDTH(
5649                   ( 4.0f / 3.0f)  * (float)rgui->frame_buf.height);
5650             base_term_width = (base_term_width < RGUI_MIN_FB_WIDTH) ?
5651                   RGUI_MIN_FB_WIDTH : base_term_width;
5652             break;
5653          case RGUI_ASPECT_RATIO_3_2:
5654             rgui->frame_buf.height = (unsigned)(
5655                   (3.0f / 2.0f) * (float)rgui->frame_buf.width);
5656             base_term_width = rgui->frame_buf.width;
5657             break;
5658          case RGUI_ASPECT_RATIO_3_2_CENTRE:
5659             rgui->frame_buf.height = (unsigned)(
5660                   (3.0f / 2.0f) * (float)rgui->frame_buf.width);
5661             base_term_width        = RGUI_ROUND_FB_WIDTH(
5662                   ( 4.0f / 3.0f)  * (float)rgui->frame_buf.height);
5663             base_term_width = (base_term_width < RGUI_MIN_FB_WIDTH) ?
5664                   RGUI_MIN_FB_WIDTH : base_term_width;
5665             break;
5666          case RGUI_ASPECT_RATIO_5_3:
5667             rgui->frame_buf.height = (unsigned)(
5668                   (5.0f / 3.0f) * (float)rgui->frame_buf.width);
5669             base_term_width = rgui->frame_buf.width;
5670             break;
5671          case RGUI_ASPECT_RATIO_5_3_CENTRE:
5672             rgui->frame_buf.height = (unsigned)(
5673                   (5.0f / 3.0f) * (float)rgui->frame_buf.width);
5674             base_term_width        = RGUI_ROUND_FB_WIDTH(
5675                   ( 4.0f / 3.0f)  * (float)rgui->frame_buf.height);
5676             base_term_width = (base_term_width < RGUI_MIN_FB_WIDTH) ?
5677                   RGUI_MIN_FB_WIDTH : base_term_width;
5678             break;
5679 
5680          default:
5681             /* 4:3 */
5682             rgui->frame_buf.height = (unsigned)(
5683                   ( 3.0f /  4.0f) * (float)rgui->frame_buf.width);
5684             base_term_width = rgui->frame_buf.width;
5685             break;
5686       }
5687    }
5688 #endif
5689 
5690    /* Allocate frame buffer */
5691    rgui->frame_buf.data = (uint16_t*)calloc(
5692          rgui->frame_buf.width * rgui->frame_buf.height, sizeof(uint16_t));
5693 
5694    if (!rgui->frame_buf.data)
5695       return false;
5696 
5697    /* Configure 'menu display' settings */
5698    gfx_display_set_width(rgui->frame_buf.width);
5699    gfx_display_set_height(rgui->frame_buf.height);
5700    gfx_display_set_framebuffer_pitch(rgui->frame_buf.width * sizeof(uint16_t));
5701 
5702    /* Determine terminal layout */
5703    rgui->term_layout.start_x  = (3 * 5) + 1;
5704    rgui->term_layout.start_y  = (3 * 5) + rgui->font_height_stride;
5705    rgui->term_layout.width    = (base_term_width - (2 * rgui->term_layout.start_x)) / rgui->font_width_stride;
5706    rgui->term_layout.height   = (rgui->frame_buf.height - (2 * rgui->term_layout.start_y)) / rgui->font_height_stride;
5707    rgui->term_layout.value_maxlen = (unsigned)((RGUI_ENTRY_VALUE_MAXLEN_FRACTION * (float)rgui->term_layout.width) + 1.0f);
5708 
5709    /* > 'Start X/Y' adjustments */
5710    rgui->term_layout.start_x  = (rgui->frame_buf.width - (rgui->term_layout.width * rgui->font_width_stride)) / 2;
5711    rgui->term_layout.start_y  = (rgui->frame_buf.height - (rgui->term_layout.height * rgui->font_height_stride)) / 2;
5712 
5713    /* Allocate background buffer */
5714    rgui->background_buf.width = rgui->frame_buf.width;
5715    rgui->background_buf.height= rgui->frame_buf.height;
5716    rgui->background_buf.data  = (uint16_t*)calloc(
5717          rgui->background_buf.width * rgui->background_buf.height, sizeof(uint16_t));
5718 
5719    if (!rgui->background_buf.data)
5720       return false;
5721 
5722    /* Allocate thumbnail buffer */
5723    rgui->fs_thumbnail.max_width    = rgui->frame_buf.width;
5724    rgui->fs_thumbnail.max_height   = rgui->frame_buf.height;
5725    rgui->fs_thumbnail.data         = (uint16_t*)calloc(
5726          rgui->fs_thumbnail.max_width * rgui->fs_thumbnail.max_height, sizeof(uint16_t));
5727    if (!rgui->fs_thumbnail.data)
5728       return false;
5729 
5730    /* Allocate mini thumbnail buffers */
5731    mini_thumbnail_term_width       = (unsigned)((float)rgui->term_layout.width * (2.0f / 5.0f));
5732    mini_thumbnail_term_width       = mini_thumbnail_term_width > 19 ? 19 : mini_thumbnail_term_width;
5733    rgui->mini_thumbnail_max_width  = mini_thumbnail_term_width * rgui->font_width_stride;
5734    rgui->mini_thumbnail_max_height = (unsigned)((rgui->term_layout.height * rgui->font_height_stride) * 0.5f) - 2;
5735 
5736    rgui->mini_thumbnail.max_width  = rgui->mini_thumbnail_max_width;
5737    rgui->mini_thumbnail.max_height = rgui->mini_thumbnail_max_height;
5738    rgui->mini_thumbnail.data       = (uint16_t*)calloc(
5739          rgui->mini_thumbnail.max_width * rgui->mini_thumbnail.max_height,
5740          sizeof(uint16_t));
5741    if (!rgui->mini_thumbnail.data)
5742       return false;
5743 
5744    rgui->mini_left_thumbnail.max_width  = rgui->mini_thumbnail_max_width;
5745    rgui->mini_left_thumbnail.max_height = rgui->mini_thumbnail_max_height;
5746    rgui->mini_left_thumbnail.data       = (uint16_t*)calloc(
5747          rgui->mini_left_thumbnail.max_width * rgui->mini_left_thumbnail.max_height,
5748          sizeof(uint16_t));
5749    if (!rgui->mini_left_thumbnail.data)
5750       return false;
5751 
5752    /* Trigger background/display update */
5753    rgui->theme_preset_path[0] = '\0';
5754    rgui->bg_modified          = true;
5755    rgui->force_redraw         = true;
5756 
5757    /* If aspect ratio lock is enabled, notify
5758     * video driver of change */
5759    if ((aspect_ratio_lock != RGUI_ASPECT_RATIO_LOCK_NONE) &&
5760        !rgui->ignore_resize_events)
5761    {
5762       rgui_update_menu_viewport(rgui, p_disp);
5763       rgui_set_video_config(rgui, &rgui->menu_video_settings, delay_update);
5764    }
5765 
5766    return true;
5767 }
5768 
rgui_menu_animation_update_time(float * ticker_pixel_increment,unsigned video_width,unsigned video_height)5769 static void rgui_menu_animation_update_time(
5770       float *ticker_pixel_increment,
5771       unsigned video_width, unsigned video_height)
5772 {
5773    /* RGUI framebuffer size is independent of
5774     * display resolution, so have to use a fixed
5775     * multiplier for smooth scrolling ticker text.
5776     * We choose a value such that text is scrolled
5777     * 1 pixel every 4 frames when ticker speed is 1x,
5778     * which matches almost exactly the scroll speed
5779     * of non-smooth ticker text (scrolling 1 pixel
5780     * every 2 frames is optimal, but may be too fast
5781     * for some users - so play it safe. Users can always
5782     * set ticker speed to 2x if they prefer) */
5783    *(ticker_pixel_increment) *= 0.25f;
5784 }
5785 
rgui_init(void ** userdata,bool video_is_threaded)5786 static void *rgui_init(void **userdata, bool video_is_threaded)
5787 {
5788    unsigned new_font_height;
5789    struct video_viewport vp;
5790    size_t               start = 0;
5791    rgui_t               *rgui = NULL;
5792    settings_t *settings       = config_get_ptr();
5793    gfx_display_t    *p_disp   = disp_get_ptr();
5794    gfx_animation_t *p_anim    = anim_get_ptr();
5795 #if defined(DINGUX)
5796    unsigned aspect_ratio_lock = RGUI_ASPECT_RATIO_LOCK_NONE;
5797 #else
5798    unsigned aspect_ratio_lock = settings->uints.menu_rgui_aspect_ratio_lock;
5799 #endif
5800    menu_handle_t        *menu = (menu_handle_t*)calloc(1, sizeof(*menu));
5801 
5802    if (!menu)
5803       return NULL;
5804 
5805    rgui                       = (rgui_t*)calloc(1, sizeof(rgui_t));
5806 
5807    if (!rgui)
5808       goto error;
5809 
5810    *userdata                  = rgui;
5811 
5812 #ifdef HAVE_GFX_WIDGETS
5813    /* We have to be somewhat careful here, since some
5814     * platforms do not like video_driver_texture-related
5815     * operations (e.g. 3DS). We would hope that these
5816     * platforms will always have HAVE_GFX_WIDGETS disabled,
5817     * but for extra safety we will only permit display widget
5818     * additions when the current gfx driver reports that it
5819     * has widget support */
5820    rgui->widgets_supported = gfx_widgets_ready();
5821 
5822    if (rgui->widgets_supported)
5823       gfx_display_init_white_texture(gfx_display_white_texture);
5824 #endif
5825 
5826    rgui->menu_title[0]    = '\0';
5827    rgui->menu_sublabel[0] = '\0';
5828 
5829    /* Set pixel format conversion function */
5830    rgui->transparency_supported = rgui_set_pixel_format_function();
5831 
5832    /* Initialise fonts */
5833    if (!rgui_fonts_init(rgui))
5834       goto error;
5835 
5836    /* Cache initial video settings */
5837    rgui_get_video_config(&rgui->content_video_settings);
5838 
5839    /* Get initial 'window' dimensions */
5840    video_driver_get_viewport_info(&vp);
5841    rgui->window_width          = vp.full_width;
5842    rgui->window_height         = vp.full_height;
5843    rgui->ignore_resize_events  = false;
5844 
5845    /* Set aspect ratio
5846     * - Allocates frame buffer
5847     * - Configures variable 'menu display' settings */
5848    rgui->menu_aspect_ratio_lock = aspect_ratio_lock;
5849    rgui->aspect_update_pending  = false;
5850    if (!rgui_set_aspect_ratio(rgui, p_disp, false))
5851       goto error;
5852 
5853    /* Fixed 'menu display' settings */
5854    new_font_height = rgui->font_height_stride * 2;
5855    p_disp->header_height = new_font_height;
5856 
5857    /* Prepare RGUI colors, to improve performance */
5858    rgui->theme_preset_path[0] = '\0';
5859    prepare_rgui_colors(rgui, settings);
5860 
5861    menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start);
5862    rgui->scroll_y = 0;
5863 
5864    rgui->bg_thickness          = settings->bools.menu_rgui_background_filler_thickness_enable;
5865    rgui->border_thickness      = settings->bools.menu_rgui_border_filler_thickness_enable;
5866    rgui->border_enable         = settings->bools.menu_rgui_border_filler_enable;
5867    rgui->shadow_enable         = settings->bools.menu_rgui_shadows;
5868    rgui->particle_effect       = settings->uints.menu_rgui_particle_effect;
5869    rgui->extended_ascii_enable = settings->bools.menu_rgui_extended_ascii;
5870 
5871    rgui->last_width            = rgui->frame_buf.width;
5872    rgui->last_height           = rgui->frame_buf.height;
5873 
5874    rgui->show_mouse            = false;
5875    rgui->show_screensaver      = false;
5876 
5877    /* Initialise particle effect, if required */
5878    if (rgui->particle_effect != RGUI_PARTICLE_EFFECT_NONE)
5879       rgui_init_particle_effect(rgui, p_disp);
5880 
5881    /* Set initial 'blit_line/symbol' functions */
5882    rgui_set_blit_functions(
5883          rgui->language,
5884          settings->bools.menu_rgui_shadows,
5885          settings->bools.menu_rgui_extended_ascii);
5886 
5887    rgui->thumbnail_path_data = gfx_thumbnail_path_init();
5888    if (!rgui->thumbnail_path_data)
5889       goto error;
5890 
5891    rgui->thumbnail_queue_size        = 0;
5892    rgui->left_thumbnail_queue_size   = 0;
5893    rgui->thumbnail_load_pending      = false;
5894    rgui->thumbnail_load_trigger_time = 0;
5895    /* Ensure that we start with fullscreen thumbnails disabled */
5896    rgui->show_fs_thumbnail           = false;
5897 
5898    /* Ensure that pointer device starts with well defined
5899     * values (shoult not be necessary, but some platforms may
5900     * not handle struct initialisation correctly...) */
5901    memset(&rgui->pointer, 0, sizeof(menu_input_pointer_t));
5902 
5903    p_anim->updatetime_cb = rgui_menu_animation_update_time;
5904 
5905    return menu;
5906 
5907 error:
5908    rgui_fonts_free(rgui);
5909 
5910    rgui_framebuffer_free(&rgui->frame_buf);
5911    rgui_framebuffer_free(&rgui->background_buf);
5912 
5913    rgui_thumbnail_free(&rgui->fs_thumbnail);
5914    rgui_thumbnail_free(&rgui->mini_thumbnail);
5915    rgui_thumbnail_free(&rgui->mini_left_thumbnail);
5916 
5917    if (menu)
5918       free(menu);
5919    return NULL;
5920 }
5921 
rgui_free(void * data)5922 static void rgui_free(void *data)
5923 {
5924    rgui_t            *rgui = (rgui_t*)data;
5925 
5926    if (rgui)
5927    {
5928 #ifdef HAVE_GFX_WIDGETS
5929       if (rgui->widgets_supported)
5930       {
5931          if (gfx_display_white_texture)
5932             video_driver_texture_unload(&gfx_display_white_texture);
5933       }
5934 #endif
5935       if (rgui->thumbnail_path_data)
5936          free(rgui->thumbnail_path_data);
5937    }
5938 
5939    rgui_fonts_free(rgui);
5940 
5941    rgui_framebuffer_free(&rgui->frame_buf);
5942    rgui_framebuffer_free(&rgui->background_buf);
5943    rgui_framebuffer_free(&rgui->upscale_buf);
5944 
5945    rgui_thumbnail_free(&rgui->fs_thumbnail);
5946    rgui_thumbnail_free(&rgui->mini_thumbnail);
5947    rgui_thumbnail_free(&rgui->mini_left_thumbnail);
5948 }
5949 
rgui_set_texture(void * data)5950 static void rgui_set_texture(void *data)
5951 {
5952    unsigned fb_width, fb_height;
5953    settings_t            *settings = config_get_ptr();
5954    gfx_display_t          *p_disp  = disp_get_ptr();
5955 #if defined(DINGUX)
5956    unsigned internal_upscale_level = RGUI_UPSCALE_NONE;
5957 #else
5958    unsigned internal_upscale_level = settings->uints.menu_rgui_internal_upscale_level;
5959 #endif
5960    rgui_t *rgui                    = (rgui_t*)data;
5961 
5962    /* Framebuffer is dirty and needs to be updated? */
5963    if (!rgui || !p_disp->framebuf_dirty)
5964       return;
5965 
5966    fb_width               = p_disp->framebuf_width;
5967    fb_height              = p_disp->framebuf_height;
5968 
5969    p_disp->framebuf_dirty = false;
5970 
5971    if (internal_upscale_level == RGUI_UPSCALE_NONE)
5972    {
5973       video_driver_set_texture_frame(rgui->frame_buf.data,
5974          false, fb_width, fb_height, 1.0f);
5975    }
5976    else
5977    {
5978       struct video_viewport vp;
5979 
5980       /* Get viewport dimensions */
5981       video_driver_get_viewport_info(&vp);
5982 
5983       /* If viewport is currently the same size (or smaller)
5984        * than the menu framebuffer, no scaling is required */
5985       if ((vp.width <= fb_width) && (vp.height <= fb_height))
5986       {
5987          video_driver_set_texture_frame(rgui->frame_buf.data,
5988             false, fb_width, fb_height, 1.0f);
5989       }
5990       else
5991       {
5992          unsigned out_width;
5993          unsigned out_height;
5994          uint32_t x_ratio, y_ratio;
5995          unsigned x_src, y_src;
5996          unsigned x_dst, y_dst;
5997          frame_buf_t *frame_buf   = &rgui->frame_buf;
5998          frame_buf_t *upscale_buf = &rgui->upscale_buf;
5999 
6000          /* Determine output size */
6001          if (internal_upscale_level == RGUI_UPSCALE_AUTO)
6002          {
6003             out_width  = ((vp.width / fb_width) + 1) * fb_width;
6004             out_height = ((vp.height / fb_height) + 1) * fb_height;
6005          }
6006          else
6007          {
6008             out_width  = internal_upscale_level * fb_width;
6009             out_height = internal_upscale_level * fb_height;
6010          }
6011 
6012          /* Allocate upscaling buffer, if required */
6013          if (  (upscale_buf->width  != out_width)  ||
6014                (upscale_buf->height != out_height) ||
6015                !upscale_buf->data)
6016          {
6017             upscale_buf->width = out_width;
6018             upscale_buf->height = out_height;
6019 
6020             if (upscale_buf->data)
6021             {
6022                free(upscale_buf->data);
6023                upscale_buf->data = NULL;
6024             }
6025 
6026             upscale_buf->data = (uint16_t*)
6027                   calloc(out_width * out_height, sizeof(uint16_t));
6028             if (!upscale_buf->data)
6029             {
6030                /* Uh oh... This could mean we don't have enough
6031                 * memory, so disable upscaling and draw the usual
6032                 * framebuffer... */
6033                configuration_set_uint(settings,
6034                      settings->uints.menu_rgui_internal_upscale_level,
6035                      RGUI_UPSCALE_NONE);
6036                video_driver_set_texture_frame(frame_buf->data,
6037                      false, fb_width, fb_height, 1.0f);
6038                return;
6039             }
6040          }
6041 
6042          /* Perform nearest neighbour upscaling
6043           * NB: We're duplicating code here, but trying to handle
6044           * this with a polymorphic function is too much of a drag... */
6045          x_ratio = ((fb_width  << 16) / out_width);
6046          y_ratio = ((fb_height << 16) / out_height);
6047 
6048          for (y_dst = 0; y_dst < out_height; y_dst++)
6049          {
6050             y_src = (y_dst * y_ratio) >> 16;
6051             for (x_dst = 0; x_dst < out_width; x_dst++)
6052             {
6053                x_src = (x_dst * x_ratio) >> 16;
6054                upscale_buf->data[(y_dst * out_width) + x_dst] = frame_buf->data[(y_src * fb_width) + x_src];
6055             }
6056          }
6057 
6058          /* Draw upscaled texture */
6059          video_driver_set_texture_frame(upscale_buf->data,
6060             false, out_width, out_height, 1.0f);
6061       }
6062    }
6063 }
6064 
rgui_navigation_clear(void * data,bool pending_push)6065 static void rgui_navigation_clear(void *data, bool pending_push)
6066 {
6067    size_t start           = 0;
6068    rgui_t           *rgui = (rgui_t*)data;
6069    if (!rgui)
6070       return;
6071 
6072    menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start);
6073    rgui->scroll_y = 0;
6074 }
6075 
rgui_set_thumbnail_system(void * userdata,char * s,size_t len)6076 static void rgui_set_thumbnail_system(void *userdata, char *s, size_t len)
6077 {
6078    rgui_t *rgui = (rgui_t*)userdata;
6079    if (!rgui)
6080       return;
6081    gfx_thumbnail_set_system(
6082          rgui->thumbnail_path_data, s, playlist_get_cached());
6083 }
6084 
rgui_get_thumbnail_system(void * userdata,char * s,size_t len)6085 static void rgui_get_thumbnail_system(void *userdata, char *s, size_t len)
6086 {
6087    rgui_t *rgui       = (rgui_t*)userdata;
6088    const char *system = NULL;
6089    if (!rgui)
6090       return;
6091    if (gfx_thumbnail_get_system(rgui->thumbnail_path_data, &system))
6092       strlcpy(s, system, len);
6093 }
6094 
rgui_load_current_thumbnails(rgui_t * rgui,bool download_missing)6095 static void rgui_load_current_thumbnails(rgui_t *rgui, bool download_missing)
6096 {
6097    const char *thumbnail_path      = NULL;
6098    const char *left_thumbnail_path = NULL;
6099    bool thumbnails_missing         = false;
6100 
6101    /* Right (or fullscreen) thumbnail */
6102    if (gfx_thumbnail_get_path(rgui->thumbnail_path_data,
6103          GFX_THUMBNAIL_RIGHT, &thumbnail_path))
6104    {
6105       rgui->entry_has_thumbnail = request_thumbnail(
6106             rgui->show_fs_thumbnail ? &rgui->fs_thumbnail : &rgui->mini_thumbnail,
6107             GFX_THUMBNAIL_RIGHT,
6108             &rgui->thumbnail_queue_size,
6109             thumbnail_path,
6110             &thumbnails_missing);
6111    }
6112 
6113    /* Left thumbnail
6114     * (Note: there is no need to load this when viewing
6115     * fullscreen thumbnails) */
6116    if (!rgui->show_fs_thumbnail)
6117    {
6118       if (gfx_thumbnail_get_path(rgui->thumbnail_path_data,
6119             GFX_THUMBNAIL_LEFT, &left_thumbnail_path))
6120       {
6121          rgui->entry_has_left_thumbnail = request_thumbnail(
6122                &rgui->mini_left_thumbnail,
6123                GFX_THUMBNAIL_LEFT,
6124                &rgui->left_thumbnail_queue_size,
6125                left_thumbnail_path,
6126                &thumbnails_missing);
6127       }
6128    }
6129 
6130    /* Reset 'load pending' state */
6131    rgui->thumbnail_load_pending = false;
6132 
6133    /* Force a redraw (so 'entry_has_thumbnail' values are
6134     * applied immediately) */
6135    rgui->force_redraw           = true;
6136 
6137 #ifdef HAVE_NETWORKING
6138    /* On demand thumbnail downloads */
6139    if (thumbnails_missing && download_missing)
6140    {
6141       const char *system = NULL;
6142 
6143       if (gfx_thumbnail_get_system(rgui->thumbnail_path_data, &system))
6144          task_push_pl_entry_thumbnail_download(system,
6145                playlist_get_cached(), (unsigned)menu_navigation_get_selection(),
6146                false, true);
6147    }
6148 #endif
6149 }
6150 
rgui_scan_selected_entry_thumbnail(rgui_t * rgui,bool force_load)6151 static void rgui_scan_selected_entry_thumbnail(rgui_t *rgui, bool force_load)
6152 {
6153    bool has_thumbnail                = false;
6154    settings_t *settings              = config_get_ptr();
6155    bool rgui_inline_thumbnails       = settings->bools.menu_rgui_inline_thumbnails;
6156    unsigned menu_rgui_thumbnail_delay= settings->uints.menu_rgui_thumbnail_delay;
6157    bool network_on_demand_thumbnails = settings->bools.network_on_demand_thumbnails;
6158    rgui->entry_has_thumbnail         = false;
6159    rgui->entry_has_left_thumbnail    = false;
6160    rgui->thumbnail_load_pending      = false;
6161 
6162    /* Update thumbnail content/path */
6163    if ((rgui->show_fs_thumbnail || rgui_inline_thumbnails)
6164          && rgui->is_playlist)
6165    {
6166       size_t selection      = menu_navigation_get_selection();
6167       size_t list_size      = menu_entries_get_size();
6168       file_list_t *list     = menu_entries_get_selection_buf_ptr(0);
6169       bool playlist_valid   = false;
6170       size_t playlist_index = selection;
6171 
6172       /* Get playlist index corresponding
6173        * to the selected entry */
6174       if (list &&
6175           (selection < list_size) &&
6176           (list->list[selection].type == FILE_TYPE_RPL_ENTRY))
6177       {
6178          playlist_valid = true;
6179          playlist_index = list->list[selection].entry_idx;
6180       }
6181 
6182       if (gfx_thumbnail_set_content_playlist(rgui->thumbnail_path_data,
6183             playlist_valid ? playlist_get_cached() : NULL, playlist_index))
6184       {
6185          if (gfx_thumbnail_is_enabled(rgui->thumbnail_path_data, GFX_THUMBNAIL_RIGHT))
6186             has_thumbnail = gfx_thumbnail_update_path(rgui->thumbnail_path_data, GFX_THUMBNAIL_RIGHT);
6187 
6188          if (rgui_inline_thumbnails &&
6189              gfx_thumbnail_is_enabled(rgui->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
6190             has_thumbnail = gfx_thumbnail_update_path(rgui->thumbnail_path_data, GFX_THUMBNAIL_LEFT) ||
6191                             has_thumbnail;
6192       }
6193    }
6194 
6195    /* Check whether thumbnails should be loaded */
6196    if (has_thumbnail)
6197    {
6198       /* Check whether thumbnails should be loaded immediately */
6199       if ((menu_rgui_thumbnail_delay == 0) || force_load)
6200          rgui_load_current_thumbnails(rgui, network_on_demand_thumbnails);
6201       else
6202       {
6203          /* Schedule a delayed load */
6204          rgui->thumbnail_load_pending      = true;
6205          rgui->thumbnail_load_trigger_time = menu_driver_get_current_time();
6206       }
6207    }
6208 }
6209 
rgui_toggle_fs_thumbnail(void * userdata)6210 static void rgui_toggle_fs_thumbnail(void *userdata)
6211 {
6212    rgui_t *rgui                = (rgui_t*)userdata;
6213    settings_t *settings        = config_get_ptr();
6214    bool rgui_inline_thumbnails = settings->bools.menu_rgui_inline_thumbnails;
6215 
6216    if (!rgui)
6217       return;
6218 
6219    rgui->show_fs_thumbnail = !rgui->show_fs_thumbnail;
6220 
6221    /* It is possible that we are waiting for a 'right' thumbnail
6222     * image to load at this point. If so, and we are displaying
6223     * inline thumbnails, then 'fs_thumbnail' and 'mini_thumbnail'
6224     * can get mixed up. To avoid this, we simply 'reset' the
6225     * currently inactive right thumbnail. */
6226    if (rgui_inline_thumbnails)
6227    {
6228       if (rgui->show_fs_thumbnail)
6229       {
6230          rgui->mini_thumbnail.width    = 0;
6231          rgui->mini_thumbnail.height   = 0;
6232          rgui->mini_thumbnail.is_valid = false;
6233          rgui->mini_thumbnail.path[0]  = '\0';
6234       }
6235       else
6236       {
6237          rgui->fs_thumbnail.width      = 0;
6238          rgui->fs_thumbnail.height     = 0;
6239          rgui->fs_thumbnail.is_valid   = false;
6240          rgui->fs_thumbnail.path[0]    = '\0';
6241       }
6242    }
6243 
6244    /* Note that we always load thumbnails immediately
6245     * when toggling via a RetroPad button (scheduling a
6246     * delayed load here would make for a poor user
6247     * experience...) */
6248    rgui_scan_selected_entry_thumbnail(rgui, true);
6249 }
6250 
rgui_refresh_thumbnail_image(void * userdata,unsigned i)6251 static void rgui_refresh_thumbnail_image(void *userdata, unsigned i)
6252 {
6253    rgui_t       *rgui          = (rgui_t*)userdata;
6254    settings_t       *settings  = config_get_ptr();
6255    bool rgui_inline_thumbnails = settings ? settings->bools.menu_rgui_inline_thumbnails : false;
6256    if (!rgui || !settings)
6257       return;
6258 
6259    /* Only refresh thumbnails if thumbnails are enabled */
6260    if ((rgui->show_fs_thumbnail || rgui_inline_thumbnails) &&
6261        (gfx_thumbnail_is_enabled(rgui->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
6262         gfx_thumbnail_is_enabled(rgui->thumbnail_path_data, GFX_THUMBNAIL_LEFT)))
6263    {
6264       /* In all cases, reset current thumbnails */
6265       rgui->fs_thumbnail.width           = 0;
6266       rgui->fs_thumbnail.height          = 0;
6267       rgui->fs_thumbnail.is_valid        = false;
6268       rgui->fs_thumbnail.path[0]         = '\0';
6269 
6270       rgui->mini_thumbnail.width         = 0;
6271       rgui->mini_thumbnail.height        = 0;
6272       rgui->mini_thumbnail.is_valid      = false;
6273       rgui->mini_thumbnail.path[0]       = '\0';
6274 
6275       rgui->mini_left_thumbnail.width    = 0;
6276       rgui->mini_left_thumbnail.height   = 0;
6277       rgui->mini_left_thumbnail.is_valid = false;
6278       rgui->mini_left_thumbnail.path[0]  = '\0';
6279 
6280       /* Only load thumbnails if currently viewing a
6281        * playlist (note that thumbnails are loaded
6282        * immediately, for an optimal user experience) */
6283       if (rgui->is_playlist)
6284          rgui_scan_selected_entry_thumbnail(rgui, true);
6285    }
6286 }
6287 
rgui_update_menu_sublabel(rgui_t * rgui)6288 static void rgui_update_menu_sublabel(rgui_t *rgui)
6289 {
6290    size_t     selection     = menu_navigation_get_selection();
6291    settings_t *settings     = config_get_ptr();
6292    bool menu_show_sublabels = settings->bools.menu_show_sublabels;
6293 
6294    rgui->menu_sublabel[0] = '\0';
6295 
6296    if (menu_show_sublabels && selection < menu_entries_get_size())
6297    {
6298       menu_entry_t entry;
6299 
6300       MENU_ENTRY_INIT(entry);
6301       entry.path_enabled       = false;
6302       entry.label_enabled      = false;
6303       entry.rich_label_enabled = false;
6304       entry.value_enabled      = false;
6305       menu_entry_get(&entry, 0, (unsigned)selection, NULL, true);
6306 
6307       if (!string_is_empty(entry.sublabel))
6308       {
6309          size_t line_index;
6310          static const char* const
6311             sublabel_spacer       = RGUI_TICKER_SPACER;
6312          bool prev_line_empty     = true;
6313          /* Sanitise sublabel
6314           * > Replace newline characters with standard delimiter
6315           * > Remove whitespace surrounding each sublabel line */
6316          struct string_list list  = {0};
6317 
6318          string_list_initialize(&list);
6319 
6320          if (string_split_noalloc(&list, entry.sublabel, "\n"))
6321          {
6322             for (line_index = 0; line_index < list.size; line_index++)
6323             {
6324                const char *line = string_trim_whitespace(
6325                      list.elems[line_index].data);
6326                if (!string_is_empty(line))
6327                {
6328                   if (!prev_line_empty)
6329                      strlcat(rgui->menu_sublabel,
6330                            sublabel_spacer, sizeof(rgui->menu_sublabel));
6331                   strlcat(rgui->menu_sublabel,
6332                         line, sizeof(rgui->menu_sublabel));
6333                   prev_line_empty = false;
6334                }
6335             }
6336          }
6337 
6338          string_list_deinitialize(&list);
6339       }
6340    }
6341 }
6342 
rgui_navigation_set(void * data,bool scroll)6343 static void rgui_navigation_set(void *data, bool scroll)
6344 {
6345    size_t start;
6346    bool do_set_start              = false;
6347    size_t end                     = menu_entries_get_size();
6348    size_t selection               = menu_navigation_get_selection();
6349    rgui_t *rgui                   = (rgui_t*)data;
6350 
6351    if (!rgui)
6352       return;
6353 
6354    rgui_scan_selected_entry_thumbnail(rgui, false);
6355    rgui_update_menu_sublabel(rgui);
6356 
6357    if (!scroll)
6358       return;
6359 
6360    if (selection < rgui->term_layout.height / 2)
6361    {
6362       start        = 0;
6363       do_set_start = true;
6364    }
6365    else if (selection >= (rgui->term_layout.height / 2)
6366          && selection < (end - rgui->term_layout.height / 2))
6367    {
6368       start        = selection - rgui->term_layout.height / 2;
6369       do_set_start = true;
6370    }
6371    else if (selection >= (end - rgui->term_layout.height / 2))
6372    {
6373       start        = end - rgui->term_layout.height;
6374       do_set_start = true;
6375    }
6376 
6377    if (do_set_start)
6378    {
6379       menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &start);
6380       rgui->scroll_y = start * rgui->font_height_stride;
6381    }
6382 }
6383 
rgui_navigation_set_last(void * data)6384 static void rgui_navigation_set_last(void *data)
6385 {
6386    rgui_navigation_set(data, true);
6387 }
6388 
rgui_navigation_descend_alphabet(void * data,size_t * unused)6389 static void rgui_navigation_descend_alphabet(void *data, size_t *unused)
6390 {
6391    rgui_navigation_set(data, true);
6392 }
6393 
rgui_navigation_ascend_alphabet(void * data,size_t * unused)6394 static void rgui_navigation_ascend_alphabet(void *data, size_t *unused)
6395 {
6396    rgui_navigation_set(data, true);
6397 }
6398 
rgui_populate_entries(void * data,const char * path,const char * label,unsigned k)6399 static void rgui_populate_entries(void *data,
6400       const char *path,
6401       const char *label, unsigned k)
6402 {
6403    rgui_t       *rgui         = (rgui_t*)data;
6404 #if defined(DINGUX)
6405    unsigned aspect_ratio_lock = RGUI_ASPECT_RATIO_LOCK_NONE;
6406 #else
6407    settings_t       *settings = config_get_ptr();
6408    unsigned aspect_ratio_lock = settings->uints.menu_rgui_aspect_ratio_lock;
6409 #endif
6410 #ifdef HAVE_LANGEXTRA
6411    gfx_display_t *p_disp  = disp_get_ptr();
6412 #endif
6413 
6414    if (!rgui)
6415       return;
6416 
6417 #ifdef HAVE_LANGEXTRA
6418    /* Check whether user language has changed */
6419    if (rgui->language != *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE))
6420    {
6421       /* Reinitialise fonts */
6422       rgui_fonts_free(rgui);
6423       rgui_fonts_init(rgui);
6424 
6425       /* Update blit_line functions */
6426       rgui_set_blit_functions(
6427             rgui->language,
6428             rgui->shadow_enable,
6429             rgui->extended_ascii_enable);
6430 
6431       /* Need to recalculate terminal dimensions
6432        * > easiest method is to call
6433        *   rgui_set_aspect_ratio() */
6434       rgui_set_aspect_ratio(rgui, p_disp, true);
6435    }
6436 #endif
6437 
6438    /* Check whether we are currently viewing a playlist */
6439    rgui->is_playlist = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_PLAYLIST_LIST)) ||
6440                        string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY)) ||
6441                        string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_FAVORITES_LIST));
6442 
6443    /* Set menu title */
6444    menu_entries_get_title(rgui->menu_title, sizeof(rgui->menu_title));
6445 
6446    /* Cancel any pending thumbnail load operations */
6447    rgui->thumbnail_load_pending = false;
6448 
6449    rgui_navigation_set(data, true);
6450 
6451    /* If aspect ratio lock is enabled, must restore
6452     * content video settings when accessing the video
6453     * scaling settings menu... */
6454    if (aspect_ratio_lock != RGUI_ASPECT_RATIO_LOCK_NONE)
6455    {
6456 #if defined(GEKKO)
6457       /* On the Wii, have to restore content video settings
6458        * at the top level video menu, otherwise changing
6459        * resolutions is cumbersome (if menu aspect ratio
6460        * is locked while this occurs, menu dimensions
6461        * go out of sync...) */
6462       if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_VIDEO_SETTINGS_LIST)))
6463 #else
6464       if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_VIDEO_SCALING_SETTINGS_LIST)))
6465 #endif
6466       {
6467          /* Make sure that any changes made while accessing
6468           * the video settings menu are preserved */
6469          rgui_video_settings_t current_video_settings = {{0}};
6470          rgui_get_video_config(&current_video_settings);
6471          if (rgui_is_video_config_equal(&current_video_settings,
6472                   &rgui->menu_video_settings))
6473          {
6474             rgui_set_video_config(rgui, &rgui->content_video_settings, false);
6475             /* Menu viewport has been overridden - must ignore
6476              * resize events until the menu is next toggled off */
6477             rgui->ignore_resize_events = true;
6478 #if !defined(GEKKO)
6479             /* Changing the video config may alter the list
6480              * of entries that should be displayed in the
6481              * video scaling menu. The current menu layout
6482              * was generated using the previous video config;
6483              * we therefore have to force a menu refresh */
6484             rgui->force_menu_refresh = true;
6485 #endif
6486          }
6487       }
6488    }
6489 }
6490 
rgui_environ(enum menu_environ_cb type,void * data,void * userdata)6491 static int rgui_environ(enum menu_environ_cb type,
6492       void *data, void *userdata)
6493 {
6494    rgui_t           *rgui = (rgui_t*)userdata;
6495    gfx_display_t *p_disp  = disp_get_ptr();
6496 
6497    if (!rgui)
6498       return -1;
6499 
6500    switch (type)
6501    {
6502       case MENU_ENVIRON_ENABLE_MOUSE_CURSOR:
6503          rgui->show_mouse          = true;
6504          p_disp->framebuf_dirty    = true;
6505          break;
6506       case MENU_ENVIRON_DISABLE_MOUSE_CURSOR:
6507          rgui->show_mouse          = false;
6508          p_disp->framebuf_dirty    = false;
6509          break;
6510       case MENU_ENVIRON_ENABLE_SCREENSAVER:
6511          rgui->show_screensaver    = true;
6512          rgui->force_redraw        = true;
6513          break;
6514       case MENU_ENVIRON_DISABLE_SCREENSAVER:
6515          rgui->show_screensaver    = false;
6516          rgui->force_redraw        = true;
6517          break;
6518       default:
6519          return -1;
6520    }
6521 
6522    return 0;
6523 }
6524 
6525 /* Forward declaration */
6526 static int rgui_menu_entry_action(
6527       void *userdata, menu_entry_t *entry,
6528       size_t i, enum menu_action action);
6529 
rgui_pointer_up(void * data,unsigned x,unsigned y,unsigned ptr,enum menu_input_pointer_gesture gesture,menu_file_list_cbs_t * cbs,menu_entry_t * entry,unsigned action)6530 static int rgui_pointer_up(void *data,
6531       unsigned x, unsigned y, unsigned ptr,
6532       enum menu_input_pointer_gesture gesture,
6533       menu_file_list_cbs_t *cbs,
6534       menu_entry_t *entry, unsigned action)
6535 {
6536    rgui_t *rgui           = (rgui_t*)data;
6537    size_t selection       = menu_navigation_get_selection();
6538 
6539    if (!rgui)
6540       return -1;
6541 
6542 
6543    switch (gesture)
6544    {
6545       case MENU_INPUT_GESTURE_TAP:
6546       case MENU_INPUT_GESTURE_SHORT_PRESS:
6547          {
6548             bool show_fs_thumbnail =
6549                rgui->show_fs_thumbnail &&
6550                rgui->entry_has_thumbnail &&
6551                (rgui->fs_thumbnail.is_valid || (rgui->thumbnail_queue_size > 0));
6552             gfx_display_t *p_disp  = disp_get_ptr();
6553             unsigned header_height = p_disp->header_height;
6554 
6555             /* Normal pointer input */
6556             if (show_fs_thumbnail)
6557             {
6558                /* If we are currently showing a fullscreen thumbnail:
6559                 * - Must provide a mechanism for toggling it off
6560                 * - A normal mouse press should just select the current
6561                 *   entry (for which the thumbnail is being shown) */
6562                if (y < header_height)
6563                   rgui_toggle_fs_thumbnail(rgui);
6564                else
6565                   return rgui_menu_entry_action(rgui, entry, selection, MENU_ACTION_SELECT);
6566             }
6567             else
6568             {
6569                if (y < header_height)
6570                   return rgui_menu_entry_action(rgui, entry, selection, MENU_ACTION_CANCEL);
6571                else if (ptr <= (menu_entries_get_size() - 1))
6572                {
6573                   /* If currently selected item matches 'pointer' value,
6574                    * perform a MENU_ACTION_SELECT on it */
6575                   if (ptr == selection)
6576                      return rgui_menu_entry_action(rgui, entry, selection, MENU_ACTION_SELECT);
6577 
6578                   /* Otherwise, just move the current selection to the
6579                    * 'pointer' value */
6580                   menu_navigation_set_selection(ptr);
6581                   rgui_navigation_set(rgui, false);
6582                }
6583             }
6584          }
6585          break;
6586       case MENU_INPUT_GESTURE_LONG_PRESS:
6587          /* 'Reset to default' action */
6588          if ((ptr <= (menu_entries_get_size() - 1)) &&
6589              (ptr == selection))
6590             return rgui_menu_entry_action(rgui, entry, selection, MENU_ACTION_START);
6591          break;
6592       default:
6593          /* Ignore input */
6594          break;
6595    }
6596 
6597    return 0;
6598 }
6599 
rgui_frame(void * data,video_frame_info_t * video_info)6600 static void rgui_frame(void *data, video_frame_info_t *video_info)
6601 {
6602    rgui_t *rgui                        = (rgui_t*)data;
6603    settings_t *settings                = config_get_ptr();
6604    bool bg_filler_thickness_enable     = settings->bools.menu_rgui_background_filler_thickness_enable;
6605    bool border_filler_thickness_enable = settings->bools.menu_rgui_border_filler_thickness_enable;
6606 #if defined(DINGUX)
6607    unsigned aspect_ratio               = RGUI_DINGUX_ASPECT_RATIO;
6608    unsigned aspect_ratio_lock          = RGUI_ASPECT_RATIO_LOCK_NONE;
6609 #else
6610    unsigned aspect_ratio               = settings->uints.menu_rgui_aspect_ratio;
6611    unsigned aspect_ratio_lock          = settings->uints.menu_rgui_aspect_ratio_lock;
6612 #endif
6613    bool border_filler_enable           = settings->bools.menu_rgui_border_filler_enable;
6614    unsigned video_width                = video_info->width;
6615    unsigned video_height               = video_info->height;
6616    gfx_display_t *p_disp               = disp_get_ptr();
6617 
6618    if (bg_filler_thickness_enable != rgui->bg_thickness)
6619    {
6620       rgui->bg_thickness = bg_filler_thickness_enable;
6621       rgui->bg_modified  = true;
6622       rgui->force_redraw = true;
6623    }
6624 
6625    if (border_filler_thickness_enable != rgui->border_thickness)
6626    {
6627       rgui->border_thickness = border_filler_thickness_enable;
6628       rgui->bg_modified      = true;
6629       rgui->force_redraw     = true;
6630    }
6631 
6632    if (border_filler_enable != rgui->border_enable)
6633    {
6634       rgui->border_enable    = border_filler_enable;
6635       rgui->bg_modified      = true;
6636       rgui->force_redraw     = true;
6637    }
6638 
6639    if (settings->bools.menu_rgui_shadows != rgui->shadow_enable)
6640    {
6641       rgui_set_blit_functions(
6642             rgui->language,
6643             settings->bools.menu_rgui_shadows,
6644             settings->bools.menu_rgui_extended_ascii);
6645 
6646       rgui->shadow_enable = settings->bools.menu_rgui_shadows;
6647       rgui->bg_modified   = true;
6648       rgui->force_redraw  = true;
6649    }
6650 
6651    if (settings->uints.menu_rgui_particle_effect != rgui->particle_effect)
6652    {
6653       rgui->particle_effect = settings->uints.menu_rgui_particle_effect;
6654 
6655       if (rgui->particle_effect != RGUI_PARTICLE_EFFECT_NONE)
6656          rgui_init_particle_effect(rgui, p_disp);
6657 
6658       rgui->force_redraw = true;
6659    }
6660 
6661    if ((rgui->particle_effect != RGUI_PARTICLE_EFFECT_NONE) &&
6662        (!rgui->show_screensaver || settings->bools.menu_rgui_particle_effect_screensaver))
6663       rgui->force_redraw = true;
6664 
6665    if (settings->bools.menu_rgui_extended_ascii != rgui->extended_ascii_enable)
6666    {
6667       rgui_set_blit_functions(
6668             rgui->language,
6669             settings->bools.menu_rgui_shadows,
6670             settings->bools.menu_rgui_extended_ascii);
6671 
6672       rgui->extended_ascii_enable = settings->bools.menu_rgui_extended_ascii;
6673       rgui->force_redraw          = true;
6674    }
6675 
6676    if ((settings->uints.menu_rgui_color_theme != rgui->color_theme) ||
6677        (rgui->transparency_supported &&
6678             (settings->bools.menu_rgui_transparency != rgui->transparency_enable)))
6679       prepare_rgui_colors(rgui, settings);
6680    else if (settings->uints.menu_rgui_color_theme == RGUI_THEME_CUSTOM)
6681    {
6682       if (string_is_not_equal_fast(settings->paths.path_rgui_theme_preset, rgui->theme_preset_path, sizeof(rgui->theme_preset_path)))
6683          prepare_rgui_colors(rgui, settings);
6684    }
6685 
6686    /* Note: both rgui_set_aspect_ratio() and rgui_set_video_config()
6687     * normally call command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL)
6688     * ## THIS CANNOT BE DONE INSIDE rgui_frame() IF THREADED VIDEO IS ENABLED ##
6689     * Attempting to do so creates a deadlock, and causes RetroArch to hang.
6690     * We therefore have to set the 'delay_update' argument, which causes
6691     * command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL) to be called at
6692     * the next instance of rgui_render() */
6693 
6694    /* > Check for changes in aspect ratio */
6695    if (aspect_ratio != rgui->menu_aspect_ratio)
6696    {
6697       /* If user changes aspect ratio directly after opening
6698        * the video scaling settings menu, then all bets are off
6699        * - we can no longer guarantee that changes to aspect ratio
6700        * and custom viewport settings will be preserved. So it
6701        * no longer makes sense to ignore resize events */
6702       rgui->ignore_resize_events = false;
6703 
6704       rgui_set_aspect_ratio(rgui, p_disp, true);
6705    }
6706 
6707    /* > Check for changes in aspect ratio lock setting */
6708    if ((aspect_ratio_lock != rgui->menu_aspect_ratio_lock) ||
6709        rgui->restore_aspect_lock)
6710    {
6711       rgui->menu_aspect_ratio_lock = aspect_ratio_lock;
6712 
6713       if (aspect_ratio_lock == RGUI_ASPECT_RATIO_LOCK_NONE)
6714          rgui_set_video_config(rgui, &rgui->content_video_settings, true);
6715       else
6716       {
6717          /* As with changes in aspect ratio, if we reach this point
6718           * after visiting the video scaling settings menu, resize
6719           * events should be monitored again */
6720          rgui->ignore_resize_events = false;
6721 
6722          rgui_update_menu_viewport(rgui, p_disp);
6723          rgui_set_video_config(rgui, &rgui->menu_video_settings, true);
6724       }
6725 
6726       /* Clear any pending 'restore aspect lock' flags */
6727       rgui->restore_aspect_lock = false;
6728    }
6729 
6730    /* > Check for changes in window (display) dimensions */
6731    if ((rgui->window_width  != video_width) ||
6732        (rgui->window_height != video_height))
6733    {
6734 #if !defined(GEKKO) && !defined(DINGUX)
6735       /* If window width or height are less than the
6736        * RGUI default size of (320-426)x240, must enable
6737        * dynamic menu 'downscaling'.
6738        * All texture buffers must be regenerated in this
6739        * case - easiest way is to just call
6740        * rgui_set_aspect_ratio()
6741        * > rgui_set_aspect_ratio() must also be called
6742        *   when transitioning from a 'downscaled' size
6743        *   back the default */
6744       unsigned default_fb_width;
6745 
6746       switch (rgui->menu_aspect_ratio)
6747       {
6748          case RGUI_ASPECT_RATIO_16_9:
6749          case RGUI_ASPECT_RATIO_16_9_CENTRE:
6750             default_fb_width = RGUI_MAX_FB_WIDTH;
6751             break;
6752          case RGUI_ASPECT_RATIO_16_10:
6753          case RGUI_ASPECT_RATIO_16_10_CENTRE:
6754             default_fb_width = 384;
6755             break;
6756          case RGUI_ASPECT_RATIO_3_2:
6757          case RGUI_ASPECT_RATIO_3_2_CENTRE:
6758             default_fb_width = 360;
6759             break;
6760          case RGUI_ASPECT_RATIO_5_3:
6761          case RGUI_ASPECT_RATIO_5_3_CENTRE:
6762             default_fb_width = 400;
6763             break;
6764          default:
6765             /* 4:3 */
6766             default_fb_width = 320;
6767             break;
6768       }
6769 
6770       if ((video_width < default_fb_width) ||
6771           (rgui->window_width < default_fb_width) ||
6772           (video_height < 240) ||
6773           (rgui->window_height < 240))
6774          rgui_set_aspect_ratio(rgui, p_disp, true);
6775 #endif
6776 
6777       /* If aspect ratio is locked, have to update viewport */
6778       if ((aspect_ratio_lock != RGUI_ASPECT_RATIO_LOCK_NONE) &&
6779           !rgui->ignore_resize_events)
6780       {
6781          rgui_update_menu_viewport(rgui, p_disp);
6782          rgui_set_video_config(rgui, &rgui->menu_video_settings, true);
6783       }
6784 
6785       rgui->window_width  = video_width;
6786       rgui->window_height = video_height;
6787    }
6788 
6789    /* Handle pending thumbnail load operations */
6790    if (rgui->thumbnail_load_pending)
6791    {
6792       /* Check whether current 'load delay' duration has elapsed
6793        * Note: Delay is increased when viewing fullscreen thumbnails,
6794        * since the flicker when switching between playlist view and
6795        * fullscreen thumbnail view is incredibly jarring...) */
6796       if ((menu_driver_get_current_time() - rgui->thumbnail_load_trigger_time) >=
6797           (settings->uints.menu_rgui_thumbnail_delay * 1000 * (rgui->show_fs_thumbnail ? 1.5f : 1.0f)))
6798          rgui_load_current_thumbnails(rgui,
6799                settings->bools.network_on_demand_thumbnails);
6800    }
6801 
6802    /* Read pointer input */
6803    if (  settings->bools.menu_mouse_enable ||
6804          settings->bools.menu_pointer_enable)
6805    {
6806       menu_input_get_pointer_state(&rgui->pointer);
6807 
6808       /* Screen must be redrawn whenever pointer is active */
6809       if ((rgui->pointer.type != MENU_POINTER_DISABLED) && rgui->pointer.active)
6810          rgui->force_redraw = true;
6811    }
6812    else
6813       rgui->pointer.type = MENU_POINTER_DISABLED;
6814 }
6815 
rgui_toggle(void * userdata,bool menu_on)6816 static void rgui_toggle(void *userdata, bool menu_on)
6817 {
6818    rgui_t               *rgui = (rgui_t*)userdata;
6819    settings_t       *settings = config_get_ptr();
6820    gfx_display_t    *p_disp   = disp_get_ptr();
6821 #if defined(DINGUX)
6822    unsigned aspect_ratio_lock = RGUI_ASPECT_RATIO_LOCK_NONE;
6823 #else
6824    unsigned aspect_ratio_lock = settings ? settings->uints.menu_rgui_aspect_ratio_lock : 0;
6825 #endif
6826 
6827    /* TODO/FIXME - when we close RetroArch, this function
6828     * gets called and settings is NULL at this point.
6829     * Maybe fundamentally change control flow so that on RetroArch
6830     * exit, this doesn't get called. */
6831    if (!rgui || !settings)
6832       return;
6833 
6834    if (aspect_ratio_lock != RGUI_ASPECT_RATIO_LOCK_NONE)
6835    {
6836       if (menu_on)
6837       {
6838          /* Cache content video settings */
6839          rgui_get_video_config(&rgui->content_video_settings);
6840 
6841          /* Update menu viewport */
6842          rgui_update_menu_viewport(rgui, p_disp);
6843 
6844          /* Apply menu video settings */
6845          rgui_set_video_config(rgui, &rgui->menu_video_settings, false);
6846       }
6847       else
6848       {
6849          /* Restore content video settings *if* user
6850           * has not changed video settings since menu was
6851           * last toggled on */
6852          rgui_video_settings_t current_video_settings = {{0}};
6853          rgui_get_video_config(&current_video_settings);
6854 
6855          if (rgui_is_video_config_equal(&current_video_settings, &rgui->menu_video_settings))
6856             rgui_set_video_config(rgui, &rgui->content_video_settings, false);
6857 
6858          /* Any modified video scaling settings have now been
6859           * registered, so it is again 'safe' to respond to window
6860           * resize events */
6861          rgui->ignore_resize_events = false;
6862       }
6863    }
6864 
6865    /* Upscaling buffer is only required while menu is on. Save
6866     * memory by freeing it whenever we switch back to the current
6867     * content */
6868    if (!menu_on && rgui->upscale_buf.data)
6869    {
6870       free(rgui->upscale_buf.data);
6871       rgui->upscale_buf.data = NULL;
6872    }
6873 }
6874 
rgui_context_reset(void * data,bool is_threaded)6875 static void rgui_context_reset(void *data, bool is_threaded)
6876 {
6877    rgui_t *rgui = (rgui_t*)data;
6878 
6879    if (!rgui)
6880       return;
6881 
6882 #ifdef HAVE_GFX_WIDGETS
6883    if (rgui->widgets_supported)
6884    {
6885       if (gfx_display_white_texture)
6886          video_driver_texture_unload(&gfx_display_white_texture);
6887       gfx_display_init_white_texture(gfx_display_white_texture);
6888    }
6889 #endif
6890    video_driver_monitor_reset();
6891 }
6892 
rgui_context_destroy(void * data)6893 static void rgui_context_destroy(void *data)
6894 {
6895    rgui_t *rgui = (rgui_t*)data;
6896 
6897    if (!rgui)
6898       return;
6899 
6900 #ifdef HAVE_GFX_WIDGETS
6901    if (rgui->widgets_supported)
6902       video_driver_texture_unload(&gfx_display_white_texture);
6903 #endif
6904 }
6905 
rgui_parse_menu_entry_action(rgui_t * rgui,menu_entry_t * entry,enum menu_action action)6906 static enum menu_action rgui_parse_menu_entry_action(
6907       rgui_t *rgui, menu_entry_t *entry,
6908       enum menu_action action)
6909 {
6910    enum menu_action new_action = action;
6911 
6912    /* Scan user inputs */
6913    switch (action)
6914    {
6915       case MENU_ACTION_OK:
6916          /* If aspect ratio lock is enabled, must restore
6917           * content video settings when saving configuration
6918           * files/overrides - otherwise RGUI's custom viewport
6919           * parameters will be included in the generated output */
6920          if ((rgui->menu_aspect_ratio_lock != RGUI_ASPECT_RATIO_LOCK_NONE) &&
6921              ((entry->enum_idx == MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CORE) ||
6922               (entry->enum_idx == MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR) ||
6923               (entry->enum_idx == MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_GAME) ||
6924               (entry->enum_idx == MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG) ||
6925               (entry->enum_idx == MENU_ENUM_LABEL_SAVE_NEW_CONFIG)))
6926          {
6927             rgui_video_settings_t current_video_settings = {{0}};
6928             rgui_get_video_config(&current_video_settings);
6929             if (rgui_is_video_config_equal(&current_video_settings,
6930                   &rgui->menu_video_settings))
6931             {
6932                /* This is identical to the temporary 'aspect
6933                 * ratio unlock' that is applied when accessing
6934                 * the video settings menu. There is, however,
6935                 * no need in this case to ignore resize events
6936                 * until the menu is next toggled off; this is a
6937                 * one-shot 'fix' that should only be active
6938                 * during the config save operation */
6939                rgui_set_video_config(rgui, &rgui->content_video_settings, false);
6940                /* Schedule a restoration of the aspect ratio
6941                 * lock on the next frame */
6942                rgui->restore_aspect_lock = true;
6943             }
6944          }
6945          break;
6946       case MENU_ACTION_SCAN:
6947       case MENU_ACTION_START:
6948          /* If this is a playlist, both the 'scan'
6949           * command and 'start' action are used to
6950           * toggle the fullscreen thumbnail view
6951           * > 'scan' is more ergonomic, which is a
6952           *   benefit for RGUI because its low
6953           *   resolution framebuffer means fullscreen
6954           *   thumbnails are likely to be viewed far
6955           *   more often than with other menu drivers
6956           * > 'start' is the regular toggle button
6957           *   for all other menu drivers, and is
6958           *   included as a fallback here for users
6959           *   with gamepads with limited numbers of
6960           *   face buttons (e.g. a NES-style pad
6961           *   does not possess a RetroPad Y/'scan'
6962           *   button) */
6963          if (rgui->is_playlist)
6964          {
6965             rgui_toggle_fs_thumbnail(rgui);
6966             new_action = MENU_ACTION_NOOP;
6967          }
6968          break;
6969       default:
6970          /* In all other cases, pass through input
6971           * menu action without intervention */
6972          break;
6973    }
6974 
6975    return new_action;
6976 }
6977 
6978 /* Menu entry action callback */
rgui_menu_entry_action(void * userdata,menu_entry_t * entry,size_t i,enum menu_action action)6979 static int rgui_menu_entry_action(
6980       void *userdata, menu_entry_t *entry,
6981       size_t i, enum menu_action action)
6982 {
6983    rgui_t *rgui = (rgui_t*)userdata;
6984 
6985    /* Process input action */
6986    enum menu_action new_action = rgui_parse_menu_entry_action(rgui,
6987          entry, action);
6988 
6989    /* Call standard generic_menu_entry_action() function */
6990    return generic_menu_entry_action(userdata, entry, i, new_action);
6991 }
6992 
6993 menu_ctx_driver_t menu_ctx_rgui = {
6994    rgui_set_texture,
6995    rgui_set_message,
6996    rgui_render,
6997    rgui_frame,
6998    rgui_init,
6999    rgui_free,
7000    rgui_context_reset,
7001    rgui_context_destroy,
7002    rgui_populate_entries,
7003    rgui_toggle,
7004    rgui_navigation_clear,
7005    NULL,
7006    NULL,
7007    rgui_navigation_set,
7008    rgui_navigation_set_last,
7009    rgui_navigation_descend_alphabet,
7010    rgui_navigation_ascend_alphabet,
7011    NULL,
7012    NULL,
7013    NULL,
7014    NULL,
7015    NULL,
7016    NULL,
7017    NULL,
7018    NULL,
7019    NULL,
7020    NULL,
7021    NULL,
7022    NULL,
7023    rgui_load_image,
7024    "rgui",
7025    rgui_environ,
7026    NULL,                               /* update_thumbnail_path */
7027    NULL,                               /* update_thumbnail_image */
7028    rgui_refresh_thumbnail_image,
7029    rgui_set_thumbnail_system,
7030    rgui_get_thumbnail_system,
7031    NULL,                               /* set_thumbnail_content */
7032    rgui_osk_ptr_at_pos,
7033    NULL,                               /* update_savestate_thumbnail_path */
7034    NULL,                               /* update_savestate_thumbnail_image */
7035    NULL,                               /* pointer_down */
7036    rgui_pointer_up,
7037    rgui_menu_entry_action
7038 };
7039