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(¤t_video_settings);
6471 if (rgui_is_video_config_equal(¤t_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(¤t_video_settings);
6854
6855 if (rgui_is_video_config_equal(¤t_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(¤t_video_settings);
6929 if (rgui_is_video_config_equal(¤t_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