1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2011-2017 - Daniel De Matteis
3  *  Copyright (C) 2014-2017 - Jean-André Santoni
4  *  Copyright (C) 2016-2019 - Brad Parker
5  *  Copyright (C) 2018      - Alfredo Monclús
6  *  Copyright (C) 2018-2020 - natinusala
7  *
8  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
9  *  of the GNU General Public License as published by the Free Software Found-
10  *  ation, either version 3 of the License, or (at your option) any later version.
11  *
12  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  *  PURPOSE.  See the GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License along with RetroArch.
17  *  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "ozone.h"
21 #include "ozone_display.h"
22 #include "ozone_theme.h"
23 
24 #include <string/stdstring.h>
25 #include <file/file_path.h>
26 #include <encodings/utf.h>
27 #include <lists/string_list.h>
28 
29 #include "../../../gfx/gfx_animation.h"
30 
31 #include "../../../input/input_osk.h"
32 
33 static void ozone_cursor_animation_cb(void *userdata);
34 
ozone_animate_cursor(ozone_handle_t * ozone,float * dst,float * target)35 static void ozone_animate_cursor(ozone_handle_t *ozone,
36       float *dst, float *target)
37 {
38    int i;
39    gfx_animation_ctx_entry_t entry;
40 
41    entry.easing_enum = EASING_OUT_QUAD;
42    entry.tag         = (uintptr_t)&ozone_default_theme;
43    entry.duration    = ANIMATION_CURSOR_PULSE;
44    entry.userdata    = ozone;
45 
46    for (i = 0; i < 16; i++)
47    {
48       if (i == 3 || i == 7 || i == 11 || i == 15)
49          continue;
50 
51       if (i == 14)
52          entry.cb = ozone_cursor_animation_cb;
53       else
54          entry.cb = NULL;
55 
56       entry.subject        = &dst[i];
57       entry.target_value   = target[i];
58 
59       gfx_animation_push(&entry);
60    }
61 }
62 
ozone_cursor_animation_cb(void * userdata)63 static void ozone_cursor_animation_cb(void *userdata)
64 {
65    float *target         = NULL;
66    ozone_handle_t *ozone = (ozone_handle_t*) userdata;
67 
68    switch (ozone->theme_dynamic_cursor_state)
69    {
70       case 0:
71          target = ozone->theme->cursor_border_1;
72          break;
73       case 1:
74          target = ozone->theme->cursor_border_0;
75          break;
76    }
77 
78    ozone->theme_dynamic_cursor_state =
79       (ozone->theme_dynamic_cursor_state + 1) % 2;
80 
81    ozone_animate_cursor(ozone, ozone->theme_dynamic.cursor_border, target);
82 }
83 
ozone_draw_cursor_slice(ozone_handle_t * ozone,gfx_display_t * p_disp,void * userdata,unsigned video_width,unsigned video_height,int x_offset,unsigned width,unsigned height,size_t y,float alpha)84 static void ozone_draw_cursor_slice(
85       ozone_handle_t *ozone,
86       gfx_display_t *p_disp,
87       void *userdata,
88       unsigned video_width,
89       unsigned video_height,
90       int x_offset,
91       unsigned width, unsigned height,
92       size_t y, float alpha)
93 {
94    float scale_factor    = ozone->last_scale_factor;
95    int slice_x           = x_offset - 12 * scale_factor;
96    int slice_y           = (int)y + 8 * scale_factor;
97    unsigned slice_new_w  = width + (24 + 1) * scale_factor;
98    unsigned slice_new_h  = height + 20 * scale_factor;
99    gfx_display_ctx_driver_t
100       *dispctx           = p_disp->dispctx;
101    static float
102       last_alpha         = 0.0f;
103 
104    if (alpha != last_alpha)
105    {
106       gfx_display_set_alpha(ozone->theme_dynamic.cursor_alpha, alpha);
107       gfx_display_set_alpha(ozone->theme_dynamic.cursor_border, alpha);
108       last_alpha = alpha;
109    }
110 
111    if (dispctx && dispctx->blend_begin)
112       dispctx->blend_begin(userdata);
113 
114    /* Cursor without border */
115    gfx_display_draw_texture_slice(
116          p_disp,
117          userdata,
118          video_width,
119          video_height,
120          slice_x,
121          slice_y,
122          80, 80,
123          slice_new_w,
124          slice_new_h,
125          video_width, video_height,
126          ozone->theme_dynamic.cursor_alpha,
127          20, scale_factor,
128          ozone->theme->textures[OZONE_THEME_TEXTURE_CURSOR_NO_BORDER]
129          );
130 
131    /* Tainted border */
132    gfx_display_draw_texture_slice(
133          p_disp,
134          userdata,
135          video_width,
136          video_height,
137          slice_x,
138          slice_y,
139          80, 80,
140          slice_new_w,
141          slice_new_h,
142          video_width, video_height,
143          ozone->theme_dynamic.cursor_border,
144          20, scale_factor,
145          ozone->textures[OZONE_TEXTURE_CURSOR_BORDER]
146          );
147 
148    if (dispctx && dispctx->blend_end)
149       dispctx->blend_end(userdata);
150 }
151 
ozone_draw_cursor_fallback(ozone_handle_t * ozone,gfx_display_t * p_disp,void * userdata,unsigned video_width,unsigned video_height,int x_offset,unsigned width,unsigned height,size_t y,float alpha)152 static void ozone_draw_cursor_fallback(
153       ozone_handle_t *ozone,
154       gfx_display_t *p_disp,
155       void *userdata,
156       unsigned video_width,
157       unsigned video_height,
158       int x_offset,
159       unsigned width, unsigned height,
160       size_t y, float alpha)
161 {
162    static float last_alpha           = 0.0f;
163 
164    if (alpha != last_alpha)
165    {
166       gfx_display_set_alpha(ozone->theme_dynamic.selection_border, alpha);
167       gfx_display_set_alpha(ozone->theme_dynamic.selection, alpha);
168       last_alpha = alpha;
169    }
170 
171    /* Fill */
172    gfx_display_draw_quad(
173          p_disp,
174          userdata,
175          video_width,
176          video_height,
177          x_offset,
178          (int)y,
179          width,
180          height - ozone->dimensions.spacer_3px,
181          video_width,
182          video_height,
183          ozone->theme_dynamic.selection);
184 
185    /* Borders (can't do one single quad because of alpha) */
186 
187    /* Top */
188    gfx_display_draw_quad(
189          p_disp,
190          userdata,
191          video_width,
192          video_height,
193          x_offset - ozone->dimensions.spacer_3px,
194          (int)(y - ozone->dimensions.spacer_3px),
195          width + ozone->dimensions.spacer_3px * 2,
196          ozone->dimensions.spacer_3px,
197          video_width,
198          video_height,
199          ozone->theme_dynamic.selection_border);
200 
201    /* Bottom */
202    gfx_display_draw_quad(
203          p_disp,
204          userdata,
205          video_width,
206          video_height,
207          x_offset - ozone->dimensions.spacer_3px,
208          (int)(y + height - ozone->dimensions.spacer_3px),
209          width + ozone->dimensions.spacer_3px * 2,
210          ozone->dimensions.spacer_3px,
211          video_width,
212          video_height,
213          ozone->theme_dynamic.selection_border);
214 
215    /* Left */
216    gfx_display_draw_quad(
217          p_disp,
218          userdata,
219          video_width,
220          video_height,
221          (int)(x_offset - ozone->dimensions.spacer_3px),
222          (int)y,
223          ozone->dimensions.spacer_3px,
224          height - ozone->dimensions.spacer_3px,
225          video_width,
226          video_height,
227          ozone->theme_dynamic.selection_border);
228 
229    /* Right */
230    gfx_display_draw_quad(
231          p_disp,
232          userdata,
233          video_width,
234          video_height,
235          x_offset + width,
236          (int)y,
237          ozone->dimensions.spacer_3px,
238          height - ozone->dimensions.spacer_3px,
239          video_width,
240          video_height,
241          ozone->theme_dynamic.selection_border);
242 }
243 
244 
245 
ozone_restart_cursor_animation(ozone_handle_t * ozone)246 void ozone_restart_cursor_animation(ozone_handle_t *ozone)
247 {
248    uintptr_t tag = (uintptr_t) &ozone_default_theme;
249 
250    if (!ozone->has_all_assets)
251       return;
252 
253    ozone->theme_dynamic_cursor_state = 1;
254    memcpy(ozone->theme_dynamic.cursor_border,
255          ozone->theme->cursor_border_0,
256          sizeof(ozone->theme_dynamic.cursor_border));
257    gfx_animation_kill_by_tag(&tag);
258 
259    ozone_animate_cursor(ozone,
260          ozone->theme_dynamic.cursor_border,
261          ozone->theme->cursor_border_1);
262 }
263 
ozone_draw_cursor(ozone_handle_t * ozone,gfx_display_t * p_disp,void * userdata,unsigned video_width,unsigned video_height,int x_offset,unsigned width,unsigned height,size_t y,float alpha)264 void ozone_draw_cursor(
265       ozone_handle_t *ozone,
266       gfx_display_t *p_disp,
267       void *userdata,
268       unsigned video_width,
269       unsigned video_height,
270       int x_offset,
271       unsigned width, unsigned height,
272       size_t y, float alpha)
273 {
274    int new_x    = x_offset;
275    size_t new_y = y;
276 
277    /* Apply wiggle animation if needed */
278    if (ozone->cursor_wiggle_state.wiggling)
279       ozone_apply_cursor_wiggle_offset(ozone, &new_x, &new_y);
280 
281    /* Draw the cursor */
282    if (ozone->has_all_assets)
283       ozone_draw_cursor_slice(ozone,
284             p_disp,
285             userdata,
286             video_width, video_height,
287             new_x, width, height, new_y, alpha);
288    else
289       ozone_draw_cursor_fallback(ozone,
290             p_disp,
291             userdata,
292             video_width,
293             video_height,
294             new_x, width, height, new_y, alpha);
295 }
296 
ozone_draw_icon(gfx_display_t * p_disp,void * userdata,unsigned video_width,unsigned video_height,unsigned icon_width,unsigned icon_height,uintptr_t texture,float x,float y,unsigned width,unsigned height,float rotation,float scale_factor,float * color)297 void ozone_draw_icon(
298       gfx_display_t *p_disp,
299       void *userdata,
300       unsigned video_width,
301       unsigned video_height,
302       unsigned icon_width,
303       unsigned icon_height,
304       uintptr_t texture,
305       float x, float y,
306       unsigned width, unsigned height,
307       float rotation, float scale_factor,
308       float *color)
309 {
310    gfx_display_ctx_rotate_draw_t rotate_draw;
311    gfx_display_ctx_draw_t draw;
312    struct video_coords coords;
313    math_matrix_4x4 mymat;
314    gfx_display_ctx_driver_t
315       *dispctx              = p_disp->dispctx;
316 
317    rotate_draw.matrix       = &mymat;
318    rotate_draw.rotation     = rotation;
319    rotate_draw.scale_x      = scale_factor;
320    rotate_draw.scale_y      = scale_factor;
321    rotate_draw.scale_z      = 1;
322    rotate_draw.scale_enable = true;
323 
324    gfx_display_rotate_z(p_disp, &rotate_draw, userdata);
325 
326    coords.vertices      = 4;
327    coords.vertex        = NULL;
328    coords.tex_coord     = NULL;
329    coords.lut_tex_coord = NULL;
330    coords.color         = (const float*)color;
331 
332    draw.x               = x;
333    draw.y               = height - y - icon_height;
334    draw.width           = icon_width;
335    draw.height          = icon_height;
336    draw.scale_factor    = scale_factor;
337    draw.rotation        = rotation;
338    draw.coords          = &coords;
339    draw.matrix_data     = &mymat;
340    draw.texture         = texture;
341    draw.prim_type       = GFX_DISPLAY_PRIM_TRIANGLESTRIP;
342    draw.pipeline_id     = 0;
343 
344    if (draw.height > 0 && draw.width > 0)
345       dispctx->draw(&draw, userdata, video_width, video_height);
346 }
347 
ozone_draw_backdrop(void * userdata,void * disp_data,unsigned video_width,unsigned video_height,float alpha)348 void ozone_draw_backdrop(
349       void *userdata,
350       void *disp_data,
351       unsigned video_width,
352       unsigned video_height,
353       float alpha)
354 {
355    static float ozone_backdrop[16] = {
356       0.00, 0.00, 0.00, 0.75,
357       0.00, 0.00, 0.00, 0.75,
358       0.00, 0.00, 0.00, 0.75,
359       0.00, 0.00, 0.00, 0.75,
360    };
361    static float last_alpha           = 0.0f;
362 
363    /* TODO: Replace this backdrop by a blur shader
364     * on the whole screen if available */
365    if (alpha != last_alpha)
366    {
367       gfx_display_set_alpha(ozone_backdrop, alpha);
368       last_alpha = alpha;
369    }
370 
371    gfx_display_draw_quad(
372          (gfx_display_t*)disp_data,
373          userdata,
374          video_width,
375          video_height,
376          0,
377          0,
378          video_width,
379          video_height,
380          video_width,
381          video_height,
382          ozone_backdrop);
383 }
384 
ozone_draw_osk(ozone_handle_t * ozone,void * userdata,void * disp_userdata,unsigned video_width,unsigned video_height,const char * label,const char * str)385 void ozone_draw_osk(ozone_handle_t *ozone,
386       void *userdata,
387       void *disp_userdata,
388       unsigned video_width,
389       unsigned video_height,
390       const char *label, const char *str)
391 {
392    unsigned i;
393    char message[2048];
394    gfx_display_t *p_disp               = (gfx_display_t*)disp_userdata;
395    const char *text                    = str;
396    unsigned text_color                 = 0xffffffff;
397    static float ozone_osk_backdrop[16] = {
398       0.00, 0.00, 0.00, 0.15,
399       0.00, 0.00, 0.00, 0.15,
400       0.00, 0.00, 0.00, 0.15,
401       0.00, 0.00, 0.00, 0.15,
402    };
403    static retro_time_t last_time  = 0;
404    struct string_list list        = {0};
405    float scale_factor             = ozone->last_scale_factor;
406    unsigned margin                = 75 * scale_factor;
407    unsigned padding               = 10 * scale_factor;
408    unsigned bottom_end            = video_height / 2;
409    unsigned y_offset              = 0;
410    bool draw_placeholder          = string_is_empty(str);
411    retro_time_t current_time      = menu_driver_get_current_time();
412 
413    if (current_time - last_time >= INTERVAL_OSK_CURSOR)
414    {
415       ozone->osk_cursor           = !ozone->osk_cursor;
416       last_time                   = current_time;
417    }
418 
419    /* Border */
420    /* Top */
421    gfx_display_draw_quad(
422          p_disp,
423          userdata,
424          video_width,
425          video_height,
426          margin,
427          margin,
428          video_width - margin*2,
429          ozone->dimensions.spacer_1px,
430          video_width,
431          video_height,
432          ozone->theme->entries_border);
433 
434    /* Bottom */
435    gfx_display_draw_quad(
436          p_disp,
437          userdata,
438          video_width,
439          video_height,
440          margin,
441          bottom_end - margin,
442          video_width - margin*2,
443          ozone->dimensions.spacer_1px,
444          video_width,
445          video_height,
446          ozone->theme->entries_border);
447 
448    /* Left */
449    gfx_display_draw_quad(
450          p_disp,
451          userdata,
452          video_width,
453          video_height,
454          margin,
455          margin,
456          ozone->dimensions.spacer_1px,
457          bottom_end - margin*2,
458          video_width,
459          video_height,
460          ozone->theme->entries_border);
461 
462    /* Right */
463    gfx_display_draw_quad(
464          p_disp,
465          userdata,
466          video_width,
467          video_height,
468          video_width - margin,
469          margin,
470          ozone->dimensions.spacer_1px,
471          bottom_end - margin*2,
472          video_width,
473          video_height,
474          ozone->theme->entries_border);
475 
476    /* Backdrop */
477    /* TODO: Remove the backdrop if blur shader is available */
478    gfx_display_draw_quad(
479          p_disp,
480          userdata,
481          video_width,
482          video_height,
483          margin + ozone->dimensions.spacer_1px,
484          margin + ozone->dimensions.spacer_1px,
485          video_width - margin*2 - ozone->dimensions.spacer_2px,
486          bottom_end - margin*2 - ozone->dimensions.spacer_2px,
487          video_width,
488          video_height,
489          ozone_osk_backdrop);
490 
491    /* Placeholder & text*/
492    if (draw_placeholder)
493    {
494       text        = label;
495       text_color  = ozone_theme_light.text_sublabel_rgba;
496    }
497 
498    (ozone->word_wrap)(message, sizeof(message), text,
499          (video_width - margin*2 - padding*2) / ozone->fonts.entries_label.glyph_width,
500          ozone->fonts.entries_label.wideglyph_width, 0);
501 
502    string_list_initialize(&list);
503    string_split_noalloc(&list, message, "\n");
504 
505    for (i = 0; i < list.size; i++)
506    {
507       const char *msg = list.elems[i].data;
508 
509       gfx_display_draw_text(
510             ozone->fonts.entries_label.font,
511             msg,
512             margin + padding * 2,       /* x */
513             margin + padding +
514             ozone->fonts.entries_label.line_height
515             + y_offset,                /* y */
516             video_width, video_height,
517             text_color,
518             TEXT_ALIGN_LEFT,
519             1.0f,
520             false,
521             1.0f,
522             false);
523 
524       /* Cursor */
525       if (i == list.size - 1)
526       {
527          if (ozone->osk_cursor)
528          {
529             unsigned cursor_x = draw_placeholder
530                ? 0
531                : font_driver_get_message_width(
532                      ozone->fonts.entries_label.font, msg,
533                      (unsigned)strlen(msg), 1);
534             gfx_display_draw_quad(
535                   p_disp,
536                   userdata,
537                   video_width,
538                   video_height,
539                     margin
540                   + padding * 2
541                   + cursor_x,
542                     margin
543                   + padding
544                   + y_offset
545                   + ozone->fonts.entries_label.line_height
546                   - ozone->fonts.entries_label.line_ascender
547                   + ozone->dimensions.spacer_3px,
548                   ozone->dimensions.spacer_1px,
549                   ozone->fonts.entries_label.line_ascender,
550                   video_width,
551                   video_height,
552                   ozone->pure_white);
553          }
554       }
555       else
556          y_offset += 25 * scale_factor;
557    }
558 
559    /* Keyboard */
560    gfx_display_draw_keyboard(
561          p_disp,
562          userdata,
563          video_width,
564          video_height,
565          ozone->theme->textures[OZONE_THEME_TEXTURE_CURSOR_STATIC],
566          ozone->fonts.entries_label.font,
567          input_event_get_osk_grid(),
568          input_event_get_osk_ptr(),
569          ozone->theme->text_rgba);
570 
571    string_list_deinitialize(&list);
572 }
573 
ozone_draw_messagebox(ozone_handle_t * ozone,gfx_display_t * p_disp,void * userdata,unsigned video_width,unsigned video_height,const char * message)574 void ozone_draw_messagebox(
575       ozone_handle_t *ozone,
576       gfx_display_t *p_disp,
577       void *userdata,
578       unsigned video_width,
579       unsigned video_height,
580       const char *message)
581 {
582    unsigned i, y_position;
583    char wrapped_message[MENU_SUBLABEL_MAX_LENGTH];
584    int x, y, longest_width  = 0;
585    int usable_width         = 0;
586    struct string_list list  = {0};
587    float scale_factor       = 0.0f;
588    unsigned width           = video_width;
589    unsigned height          = video_height;
590    gfx_display_ctx_driver_t
591       *dispctx              = p_disp->dispctx;
592 
593    wrapped_message[0]       = '\0';
594 
595    /* Sanity check */
596    if (string_is_empty(message) ||
597        !ozone->fonts.footer.font)
598       return;
599 
600    scale_factor = ozone->last_scale_factor;
601    usable_width = (int)width - (48 * 8 * scale_factor);
602 
603    if (usable_width < 1)
604       return;
605 
606    /* Split message into lines */
607    (ozone->word_wrap)(
608          wrapped_message, sizeof(wrapped_message), message,
609          usable_width / (int)ozone->fonts.footer.glyph_width,
610          ozone->fonts.footer.wideglyph_width, 0);
611 
612    string_list_initialize(&list);
613    if (
614             !string_split_noalloc(&list, wrapped_message, "\n")
615          || list.elems == 0)
616    {
617       string_list_deinitialize(&list);
618       return;
619    }
620 
621    y_position       = height / 2;
622    if (menu_input_dialog_get_display_kb())
623       y_position    = height / 4;
624 
625    x                = width  / 2;
626    y                = y_position - (list.size
627          * ozone->fonts.footer.line_height) / 2;
628 
629    /* find the longest line width */
630    for (i = 0; i < list.size; i++)
631    {
632       const char *msg  = list.elems[i].data;
633 
634       if (!string_is_empty(msg))
635       {
636          int width = font_driver_get_message_width(
637                ozone->fonts.footer.font, msg, (unsigned)strlen(msg), 1);
638 
639          if (width > longest_width)
640             longest_width = width;
641       }
642    }
643 
644    gfx_display_set_alpha(ozone->theme_dynamic.message_background, ozone->animations.messagebox_alpha);
645 
646    if (dispctx && dispctx->blend_begin)
647       dispctx->blend_begin(userdata);
648 
649    /* Avoid drawing a black box if there's no assets */
650    if (ozone->has_all_assets)
651    {
652       /* Note: The fact that we use a texture slice here
653        * makes things very messy
654        * > The actual size and offset of a texture slice
655        *   is quite 'loose', and depends upon source image
656        *   size, draw size and scale factor... */
657       unsigned slice_new_w = longest_width + 48 * 2 * scale_factor;
658       unsigned slice_new_h = ozone->fonts.footer.line_height * (list.size + 2);
659       int slice_x          = x - longest_width/2 - 48 * scale_factor;
660       int slice_y          = y - ozone->fonts.footer.line_height +
661             ((slice_new_h >= 256)
662              ? (16.0f * scale_factor)
663              : (16.0f * ((float)slice_new_h / 256.0f)));
664 
665       gfx_display_draw_texture_slice(
666             p_disp,
667             userdata,
668             video_width,
669             video_height,
670             slice_x,
671             slice_y,
672             256, 256,
673             slice_new_w,
674             slice_new_h,
675             width, height,
676             ozone->theme_dynamic.message_background,
677             16, scale_factor,
678             ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_DIALOG_SLICE]
679             );
680    }
681 
682    for (i = 0; i < list.size; i++)
683    {
684       const char *msg = list.elems[i].data;
685 
686       if (msg)
687          gfx_display_draw_text(
688                ozone->fonts.footer.font,
689                msg,
690                x - longest_width/2.0,
691                y + (i * ozone->fonts.footer.line_height) +
692                ozone->fonts.footer.line_ascender,
693                width,
694                height,
695                COLOR_TEXT_ALPHA(ozone->theme->text_rgba, (uint32_t)(ozone->animations.messagebox_alpha*255.0f)),
696                TEXT_ALIGN_LEFT,
697                1.0f,
698                false,
699                1.0f,
700                false);
701    }
702 
703    string_list_deinitialize(&list);
704 }
705 
ozone_draw_fullscreen_thumbnails(ozone_handle_t * ozone,void * userdata,void * disp_userdata,unsigned video_width,unsigned video_height)706 void ozone_draw_fullscreen_thumbnails(
707       ozone_handle_t *ozone,
708       void *userdata,
709       void *disp_userdata,
710       unsigned video_width,
711       unsigned video_height)
712 {
713    /* Check whether fullscreen thumbnails are visible */
714    if (ozone->animations.fullscreen_thumbnail_alpha > 0.0f)
715    {
716       /* Note: right thumbnail is drawn at the top
717        * in the sidebar, so it becomes the *left*
718        * thumbnail when viewed fullscreen */
719       gfx_thumbnail_t *right_thumbnail  = &ozone->thumbnails.left;
720       gfx_thumbnail_t *left_thumbnail   = &ozone->thumbnails.right;
721       unsigned width                    = video_width;
722       unsigned height                   = video_height;
723       int view_width                    = (int)width;
724       gfx_display_t *p_disp             = (gfx_display_t*)disp_userdata;
725 
726       int view_height                   = (int)height - ozone->dimensions.header_height - ozone->dimensions.footer_height - ozone->dimensions.spacer_1px;
727       int thumbnail_margin              = ozone->dimensions.fullscreen_thumbnail_padding;
728       bool show_right_thumbnail         = false;
729       bool show_left_thumbnail          = false;
730       unsigned num_thumbnails           = 0;
731       float right_thumbnail_draw_width  = 0.0f;
732       float right_thumbnail_draw_height = 0.0f;
733       float left_thumbnail_draw_width   = 0.0f;
734       float left_thumbnail_draw_height  = 0.0f;
735       float background_alpha            = 0.85f;
736       static float background_color[16] = {
737          0.0f, 0.0f, 0.0f, 1.0f,
738          0.0f, 0.0f, 0.0f, 1.0f,
739          0.0f, 0.0f, 0.0f, 1.0f,
740          0.0f, 0.0f, 0.0f, 1.0f,
741       };
742       int frame_width                   = (int)((float)thumbnail_margin / 3.0f);
743       float frame_color[16];
744       float separator_color[16];
745       int thumbnail_box_width;
746       int thumbnail_box_height;
747       int right_thumbnail_x;
748       int left_thumbnail_x;
749       int thumbnail_y;
750 
751       /* Sanity check: Return immediately if this is
752        * a menu without thumbnails and we are not currently
753        * 'fading out' the fullscreen thumbnail view */
754       if (!ozone->fullscreen_thumbnails_available &&
755           ozone->show_fullscreen_thumbnails)
756          goto error;
757 
758       /* Safety check: ensure that current
759        * selection matches the entry selected when
760        * fullscreen thumbnails were enabled
761        * > Note that we exclude this check if we are
762        *   currently viewing the quick menu and the
763        *   thumbnail view is fading out. This enables
764        *   a smooth transition if the user presses
765        *   RetroPad A or keyboard 'return' to enter the
766        *   quick menu while fullscreen thumbnails are
767        *   being displayed */
768       if (((size_t)ozone->selection != ozone->fullscreen_thumbnail_selection) &&
769           (!ozone->is_quick_menu || ozone->show_fullscreen_thumbnails))
770          goto error;
771 
772       /* Sanity check: Return immediately if the view
773        * width/height is < 1 */
774       if ((view_width < 1) || (view_height < 1))
775          goto error;
776 
777       /* Get number of 'active' thumbnails */
778       show_right_thumbnail = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE);
779       show_left_thumbnail  = (left_thumbnail->status  == GFX_THUMBNAIL_STATUS_AVAILABLE);
780 
781       if (show_right_thumbnail)
782          num_thumbnails++;
783 
784       if (show_left_thumbnail)
785          num_thumbnails++;
786 
787       /* Do nothing if both thumbnails are missing
788        * > Note: Baring inexplicable internal errors, this
789        *   can never happen... */
790       if (num_thumbnails < 1)
791          goto error;
792 
793       /* Get base thumbnail dimensions + draw positions */
794 
795       /* > Thumbnail bounding box height + y position
796        *   are fixed */
797       thumbnail_box_height = view_height - (thumbnail_margin * 2);
798       thumbnail_y          = ozone->dimensions.header_height + thumbnail_margin + ozone->dimensions.spacer_1px;
799 
800       /* Thumbnail bounding box width and x position
801        * depend upon number of active thumbnails */
802       if (num_thumbnails == 2)
803       {
804          thumbnail_box_width = (view_width - (thumbnail_margin * 3) - frame_width) >> 1;
805          left_thumbnail_x    = thumbnail_margin;
806          right_thumbnail_x   = left_thumbnail_x + thumbnail_box_width + frame_width + thumbnail_margin;
807       }
808       else
809       {
810          thumbnail_box_width = view_width - (thumbnail_margin * 2);
811          left_thumbnail_x    = thumbnail_margin;
812          right_thumbnail_x   = left_thumbnail_x;
813       }
814 
815       /* Sanity check */
816       if ((thumbnail_box_width < 1) ||
817           (thumbnail_box_height < 1))
818          goto error;
819 
820       /* Get thumbnail draw dimensions
821        * > Note: The following code is a bit awkward, since
822        *   we have to do things in a very specific order
823        *   - i.e. we cannot determine proper thumbnail
824        *     layout until we have thumbnail draw dimensions.
825        *     and we cannot get draw dimensions until we have
826        *     the bounding box dimensions...  */
827       if (show_right_thumbnail)
828       {
829          gfx_thumbnail_get_draw_dimensions(
830                right_thumbnail,
831                thumbnail_box_width, thumbnail_box_height, 1.0f,
832                &right_thumbnail_draw_width, &right_thumbnail_draw_height);
833 
834          /* Sanity check */
835          if ((right_thumbnail_draw_width <= 0.0f) ||
836              (right_thumbnail_draw_height <= 0.0f))
837             goto error;
838       }
839 
840       if (show_left_thumbnail)
841       {
842          gfx_thumbnail_get_draw_dimensions(
843                left_thumbnail,
844                thumbnail_box_width, thumbnail_box_height, 1.0f,
845                &left_thumbnail_draw_width, &left_thumbnail_draw_height);
846 
847          /* Sanity check */
848          if ((left_thumbnail_draw_width <= 0.0f) ||
849              (left_thumbnail_draw_height <= 0.0f))
850             goto error;
851       }
852 
853       /* Adjust thumbnail draw positions to achieve
854        * uniform appearance (accounting for actual
855        * draw dimensions...) */
856       if (num_thumbnails == 2)
857       {
858          int left_padding  = (thumbnail_box_width - (int)left_thumbnail_draw_width)  >> 1;
859          int right_padding = (thumbnail_box_width - (int)right_thumbnail_draw_width) >> 1;
860 
861          /* Move thumbnails as close together as possible,
862           * and horizontally centre the resultant 'block'
863           * of images */
864          left_thumbnail_x  += right_padding;
865          right_thumbnail_x -= left_padding;
866       }
867 
868       /* Set colour values */
869 
870       /* > Background */
871       gfx_display_set_alpha(
872             background_color,
873             background_alpha * ozone->animations.fullscreen_thumbnail_alpha);
874 
875       /* > Separators */
876       memcpy(separator_color, ozone->theme->header_footer_separator, sizeof(separator_color));
877       gfx_display_set_alpha(
878             separator_color, ozone->animations.fullscreen_thumbnail_alpha);
879 
880       /* > Thumbnail frame */
881       memcpy(frame_color, ozone->theme->sidebar_background, sizeof(frame_color));
882       gfx_display_set_alpha(
883             frame_color, ozone->animations.fullscreen_thumbnail_alpha);
884 
885       /* Darken background */
886       gfx_display_draw_quad(
887             p_disp,
888             userdata,
889             video_width,
890             video_height,
891             0,
892             ozone->dimensions.header_height + ozone->dimensions.spacer_1px,
893             width,
894             (unsigned)view_height,
895             width,
896             height,
897             background_color);
898 
899       /* Draw full-width separators */
900       gfx_display_draw_quad(
901             p_disp,
902             userdata,
903             video_width,
904             video_height,
905             0,
906             ozone->dimensions.header_height,
907             width,
908             ozone->dimensions.spacer_1px,
909             width,
910             height,
911             separator_color);
912 
913       gfx_display_draw_quad(
914             p_disp,
915             userdata,
916             video_width,
917             video_height,
918             0,
919             height - ozone->dimensions.footer_height,
920             width,
921             ozone->dimensions.spacer_1px,
922             width,
923             height,
924             separator_color);
925 
926       /* Draw thumbnails */
927 
928       /* > Right */
929       if (show_right_thumbnail)
930       {
931          /* Background */
932          gfx_display_draw_quad(
933                p_disp,
934                userdata,
935                video_width,
936                video_height,
937                right_thumbnail_x - frame_width +
938                      ((thumbnail_box_width - (int)right_thumbnail_draw_width) >> 1),
939                thumbnail_y - frame_width +
940                      ((thumbnail_box_height - (int)right_thumbnail_draw_height) >> 1),
941                (unsigned)right_thumbnail_draw_width + (frame_width << 1),
942                (unsigned)right_thumbnail_draw_height + (frame_width << 1),
943                width,
944                height,
945                frame_color);
946 
947          /* Thumbnail */
948          gfx_thumbnail_draw(
949                userdata,
950                video_width,
951                video_height,
952                right_thumbnail,
953                right_thumbnail_x,
954                thumbnail_y,
955                (unsigned)thumbnail_box_width,
956                (unsigned)thumbnail_box_height,
957                GFX_THUMBNAIL_ALIGN_CENTRE,
958                ozone->animations.fullscreen_thumbnail_alpha,
959                1.0f,
960                NULL);
961       }
962 
963       /* > Left */
964       if (show_left_thumbnail)
965       {
966          /* Background */
967          gfx_display_draw_quad(
968                p_disp,
969                userdata,
970                video_width,
971                video_height,
972                left_thumbnail_x - frame_width +
973                      ((thumbnail_box_width - (int)left_thumbnail_draw_width) >> 1),
974                thumbnail_y - frame_width +
975                      ((thumbnail_box_height - (int)left_thumbnail_draw_height) >> 1),
976                (unsigned)left_thumbnail_draw_width + (frame_width << 1),
977                (unsigned)left_thumbnail_draw_height + (frame_width << 1),
978                width,
979                height,
980                frame_color);
981 
982          /* Thumbnail */
983          gfx_thumbnail_draw(
984                userdata,
985                video_width,
986                video_height,
987                left_thumbnail,
988                left_thumbnail_x,
989                thumbnail_y,
990                (unsigned)thumbnail_box_width,
991                (unsigned)thumbnail_box_height,
992                GFX_THUMBNAIL_ALIGN_CENTRE,
993                ozone->animations.fullscreen_thumbnail_alpha,
994                1.0f,
995                NULL);
996       }
997    }
998 
999    return;
1000 
1001 error:
1002    /* If fullscreen thumbnails are enabled at
1003     * this point, must disable them immediately... */
1004    if (ozone->show_fullscreen_thumbnails)
1005       ozone_hide_fullscreen_thumbnails(ozone, false);
1006 }
1007