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      - natinusala
7  *  Copyright (C) 2019      - Patrick Scheurenbrand
8  *
9  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
10  *  of the GNU General Public License as published by the Free Software Found-
11  *  ation, either version 3 of the License, or (at your option) any later version.
12  *
13  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
14  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  *  PURPOSE.  See the GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License along with RetroArch.
18  *  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "ozone.h"
22 #include "ozone_texture.h"
23 #include "ozone_display.h"
24 
25 #include <string/stdstring.h>
26 #include <encodings/utf.h>
27 
28 #include "../../menu_driver.h"
29 #include "../../../gfx/gfx_animation.h"
30 
31 #include "../../../configuration.h"
32 
ozone_get_entries_padding(ozone_handle_t * ozone,bool old_list)33 static int ozone_get_entries_padding(ozone_handle_t* ozone, bool old_list)
34 {
35    if (ozone->depth == 1)
36    {
37       if (!old_list)
38          return ozone->dimensions.entry_padding_horizontal_half;
39    }
40    else if (ozone->depth == 2)
41    {
42       if (old_list && !ozone->fade_direction) /* false = left to right */
43          return ozone->dimensions.entry_padding_horizontal_half;
44    }
45    return ozone->dimensions.entry_padding_horizontal_full;
46 }
47 
ozone_draw_entry_value(ozone_handle_t * ozone,gfx_display_t * p_disp,void * userdata,unsigned video_width,unsigned video_height,char * value,unsigned x,unsigned y,uint32_t alpha_uint32,menu_entry_t * entry)48 static void ozone_draw_entry_value(
49       ozone_handle_t *ozone,
50       gfx_display_t *p_disp,
51       void *userdata,
52       unsigned video_width,
53       unsigned video_height,
54       char *value,
55       unsigned x, unsigned y,
56       uint32_t alpha_uint32,
57       menu_entry_t *entry)
58 {
59    bool switch_is_on                 = true;
60    bool do_draw_text                 = false;
61    float scale_factor                = ozone->last_scale_factor;
62    gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
63 
64    /* check icon */
65    if (entry->checked)
66    {
67       float *col = ozone->theme_dynamic.entries_checkmark;
68       if (dispctx && dispctx->blend_begin)
69          dispctx->blend_begin(userdata);
70       ozone_draw_icon(
71             p_disp,
72             userdata,
73             video_width,
74             video_height,
75             30 * scale_factor,
76             30 * scale_factor,
77             ozone->theme->textures[OZONE_THEME_TEXTURE_CHECK],
78             x - 20 * scale_factor,
79             y - 22 * scale_factor,
80             video_width,
81             video_height,
82             0,
83             1,
84             col);
85       if (dispctx && dispctx->blend_end)
86          dispctx->blend_end(userdata);
87       return;
88    }
89    else if (string_is_empty(value))
90       return;
91 
92 
93    /* text value */
94    if (string_is_equal(value, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED)) ||
95          (string_is_equal(value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF))))
96       switch_is_on = false;
97    else if (string_is_equal(value, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED)) ||
98          (string_is_equal(value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON)))) { }
99    else
100    {
101       if (!string_is_empty(entry->value))
102       {
103          if (string_is_equal(entry->value, "..."))
104             return;
105          if (string_starts_with_size(entry->value, "(", STRLEN_CONST("(")) &&
106                string_ends_with  (entry->value, ")")
107             )
108          {
109             if (
110                   string_is_equal(entry->value, "(PRESET)")  ||
111                   string_is_equal(entry->value, "(SHADER)")  ||
112                   string_is_equal(entry->value, "(COMP)")  ||
113                   string_is_equal(entry->value, "(CORE)")  ||
114                   string_is_equal(entry->value, "(MOVIE)") ||
115                   string_is_equal(entry->value, "(MUSIC)") ||
116                   string_is_equal(entry->value, "(DIR)")   ||
117                   string_is_equal(entry->value, "(RDB)")   ||
118                   string_is_equal(entry->value, "(CURSOR)")||
119                   string_is_equal(entry->value, "(CFILE)") ||
120                   string_is_equal(entry->value, "(FILE)")  ||
121                   string_is_equal(entry->value, "(IMAGE)")
122                )
123                return;
124          }
125       }
126 
127       do_draw_text = true;
128    }
129 
130    if (do_draw_text)
131    {
132       gfx_display_draw_text(
133             ozone->fonts.entries_label.font,
134             value,
135             x,
136             y,
137             video_width,
138             video_height,
139             COLOR_TEXT_ALPHA(ozone->theme->text_selected_rgba, alpha_uint32),
140             TEXT_ALIGN_RIGHT,
141             1.0f,
142             false,
143             1.0f,
144             false);
145    }
146    else
147       gfx_display_draw_text(
148             ozone->fonts.entries_label.font,
149             (switch_is_on ? msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON) : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF)),
150             x,
151             y,
152             video_width,
153             video_height,
154             COLOR_TEXT_ALPHA((switch_is_on ? ozone->theme->text_selected_rgba : ozone->theme->text_sublabel_rgba), alpha_uint32),
155             TEXT_ALIGN_RIGHT,
156             1.0f,
157             false,
158             1.0f,
159             false);
160 }
161 
ozone_thumbnail_bar_hide_end(void * userdata)162 static void ozone_thumbnail_bar_hide_end(void *userdata)
163 {
164    ozone_handle_t *ozone             = (ozone_handle_t*) userdata;
165    ozone->show_thumbnail_bar         = false;
166    ozone->pending_hide_thumbnail_bar = false;
167 }
168 
ozone_draw_no_thumbnail_available(ozone_handle_t * ozone,gfx_display_t * p_disp,void * userdata,unsigned video_width,unsigned video_height,unsigned x_position,unsigned sidebar_width,unsigned y_offset)169 static void ozone_draw_no_thumbnail_available(
170       ozone_handle_t *ozone,
171       gfx_display_t *p_disp,
172       void *userdata,
173       unsigned video_width,
174       unsigned video_height,
175       unsigned x_position,
176       unsigned sidebar_width,
177       unsigned y_offset)
178 {
179    unsigned icon                     = OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO;
180    unsigned icon_size                = (unsigned)((float)
181          ozone->dimensions.sidebar_entry_icon_size * 1.5f);
182    gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
183    float                        *col = ozone->theme->entries_icon;
184 
185    if (dispctx)
186    {
187       if (dispctx->blend_begin)
188          dispctx->blend_begin(userdata);
189       if (dispctx->draw)
190          ozone_draw_icon(
191                p_disp,
192                userdata,
193                video_width,
194                video_height,
195                icon_size,
196                icon_size,
197                ozone->icons_textures[icon],
198                x_position + sidebar_width/2 - icon_size/2,
199                video_height/2 - icon_size/2 - y_offset,
200                video_width,
201                video_height,
202                0, 1, col);
203       if (dispctx->blend_end)
204          dispctx->blend_end(userdata);
205    }
206 
207    gfx_display_draw_text(
208          ozone->fonts.footer.font,
209          msg_hash_to_str(MSG_NO_THUMBNAIL_AVAILABLE),
210          x_position + sidebar_width   / 2,
211            video_height / 2 + icon_size / 2
212          + ozone->fonts.footer.line_ascender - y_offset,
213          video_width,
214          video_height,
215          ozone->theme->text_rgba,
216          TEXT_ALIGN_CENTER,
217          1.0f,
218          false,
219          1.0f,
220          true);
221 }
222 
ozone_content_metadata_line(unsigned video_width,unsigned video_height,ozone_handle_t * ozone,unsigned * y,unsigned column_x,const char * text,uint32_t color,unsigned lines_count)223 static void ozone_content_metadata_line(
224       unsigned video_width,
225       unsigned video_height,
226       ozone_handle_t *ozone,
227       unsigned *y,
228       unsigned column_x,
229       const char *text,
230       uint32_t color,
231       unsigned lines_count)
232 {
233    gfx_display_draw_text(
234          ozone->fonts.footer.font,
235          text,
236          column_x,
237          *y + ozone->fonts.footer.line_ascender,
238          video_width,
239          video_height,
240          color,
241          TEXT_ALIGN_LEFT,
242          1.0f,
243          false,
244          1.0f,
245          true);
246 
247    if (lines_count > 0)
248       *y += (unsigned)(ozone->fonts.footer.line_height * (lines_count - 1)) + (unsigned)((float)ozone->fonts.footer.line_height * 1.5f);
249 }
250 
251 
252 /* Compute new scroll position
253  * If the center of the currently selected entry is not in the middle
254  * And if we can scroll so that it's in the middle
255  * Then scroll
256  */
ozone_update_scroll(ozone_handle_t * ozone,bool allow_animation,ozone_node_t * node)257 void ozone_update_scroll(ozone_handle_t *ozone, bool allow_animation, ozone_node_t *node)
258 {
259    unsigned video_info_height;
260    gfx_animation_ctx_entry_t entry;
261    float new_scroll = 0, entries_middle;
262    float bottom_boundary, current_selection_middle_onscreen;
263    file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0);
264    uintptr_t tag              = (uintptr_t) selection_buf;
265 
266    video_driver_get_size(NULL, &video_info_height);
267 
268    current_selection_middle_onscreen    =
269       ozone->dimensions.header_height +
270       ozone->dimensions.entry_padding_vertical +
271       ozone->animations.scroll_y +
272       node->position_y +
273       node->height / 2;
274 
275    bottom_boundary                      = video_info_height - ozone->dimensions.header_height - ozone->dimensions.spacer_1px - ozone->dimensions.footer_height;
276    entries_middle                       = video_info_height/2;
277 
278    new_scroll = ozone->animations.scroll_y - (current_selection_middle_onscreen - entries_middle);
279 
280    if (new_scroll + ozone->entries_height < bottom_boundary)
281       new_scroll = bottom_boundary - ozone->entries_height - ozone->dimensions.entry_padding_vertical * 2;
282 
283    if (new_scroll > 0)
284       new_scroll = 0;
285 
286    /* Kill any existing scroll animation */
287    gfx_animation_kill_by_tag(&tag);
288 
289    /* ozone->animations.scroll_y will be modified
290     * > Set scroll acceleration to zero to minimise
291     *   potential conflicts */
292    menu_input_set_pointer_y_accel(0.0f);
293 
294    if (allow_animation)
295    {
296       /* Cursor animation */
297       ozone->animations.cursor_alpha = 0.0f;
298 
299       entry.cb             = NULL;
300       entry.duration       = ANIMATION_CURSOR_DURATION;
301       entry.easing_enum    = EASING_OUT_QUAD;
302       entry.subject        = &ozone->animations.cursor_alpha;
303       entry.tag            = tag;
304       entry.target_value   = 1.0f;
305       entry.userdata       = NULL;
306 
307       gfx_animation_push(&entry);
308 
309       /* Scroll animation */
310       entry.cb             = NULL;
311       entry.duration       = ANIMATION_CURSOR_DURATION;
312       entry.easing_enum    = EASING_OUT_QUAD;
313       entry.subject        = &ozone->animations.scroll_y;
314       entry.tag            = tag;
315       entry.target_value   = new_scroll;
316       entry.userdata       = NULL;
317 
318       gfx_animation_push(&entry);
319    }
320    else
321    {
322       ozone->selection_old = ozone->selection;
323       ozone->animations.scroll_y = new_scroll;
324    }
325 }
326 
ozone_compute_entries_position(ozone_handle_t * ozone,settings_t * settings,size_t entries_end)327 void ozone_compute_entries_position(
328       ozone_handle_t *ozone,
329       settings_t *settings,
330       size_t entries_end)
331 {
332    /* Compute entries height and adjust scrolling if needed */
333    unsigned video_info_height;
334    unsigned video_info_width;
335    size_t i;
336    file_list_t *selection_buf    = NULL;
337    int entry_padding             = ozone_get_entries_padding(ozone, false);
338    float scale_factor            = ozone->last_scale_factor;
339    bool menu_show_sublabels      = settings->bools.menu_show_sublabels;
340 
341    menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &i);
342 
343    selection_buf                 = menu_entries_get_selection_buf_ptr(0);
344 
345    video_driver_get_size(&video_info_width, &video_info_height);
346 
347    ozone->entries_height = 0;
348 
349    for (i = 0; i < entries_end; i++)
350    {
351       /* Entry */
352       menu_entry_t entry;
353       ozone_node_t *node       = NULL;
354 
355       MENU_ENTRY_INIT(entry);
356       entry.path_enabled       = false;
357       entry.label_enabled      = false;
358       entry.rich_label_enabled = false;
359       entry.value_enabled      = false;
360       menu_entry_get(&entry, 0, (unsigned)i, NULL, true);
361 
362       /* Empty playlist detection:
363          only one item which icon is
364          OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO */
365       if (ozone->is_playlist && entries_end == 1)
366       {
367          uintptr_t         tex = ozone_entries_icon_get_texture(ozone, entry.enum_idx, entry.type, false);
368          ozone->empty_playlist = tex == ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO];
369       }
370       else
371          ozone->empty_playlist = false;
372 
373       /* Cache node */
374       node                     = (ozone_node_t*)selection_buf->list[i].userdata;
375 
376       if (!node)
377          continue;
378 
379       node->height             = ozone->dimensions.entry_height;
380       node->wrap               = false;
381       node->sublabel_lines     = 0;
382 
383       if (menu_show_sublabels)
384       {
385          if (!string_is_empty(entry.sublabel))
386          {
387             int sublabel_max_width;
388             char wrapped_sublabel_str[MENU_SUBLABEL_MAX_LENGTH];
389             wrapped_sublabel_str[0] = '\0';
390 
391             node->height += ozone->dimensions.entry_spacing + 40 * scale_factor;
392 
393             sublabel_max_width = video_info_width -
394                entry_padding * 2 - ozone->dimensions.entry_icon_padding * 2;
395 
396             if (ozone->depth == 1)
397             {
398                sublabel_max_width -= (unsigned) ozone->dimensions_sidebar_width;
399 
400                if (ozone->show_thumbnail_bar)
401                   sublabel_max_width -= ozone->dimensions.thumbnail_bar_width;
402             }
403 
404             (ozone->word_wrap)(wrapped_sublabel_str, sizeof(wrapped_sublabel_str), entry.sublabel,
405                   sublabel_max_width /
406                   ozone->fonts.entries_sublabel.glyph_width,
407                   ozone->fonts.entries_sublabel.wideglyph_width, 0);
408 
409             node->sublabel_lines = ozone_count_lines(wrapped_sublabel_str);
410 
411             if (node->sublabel_lines > 1)
412             {
413                node->height += (node->sublabel_lines - 1) * ozone->fonts.entries_sublabel.line_height;
414                node->wrap = true;
415             }
416          }
417       }
418 
419       node->position_y = ozone->entries_height;
420 
421       ozone->entries_height += node->height;
422    }
423 
424    /* Update scrolling */
425    ozone->selection = menu_navigation_get_selection();
426    ozone_update_scroll(ozone, false, (ozone_node_t*)selection_buf->list[ozone->selection].userdata);
427 }
428 
ozone_entries_update_thumbnail_bar(ozone_handle_t * ozone,bool is_playlist,bool allow_animation)429 void ozone_entries_update_thumbnail_bar(ozone_handle_t *ozone, bool is_playlist, bool allow_animation)
430 {
431    struct gfx_animation_ctx_entry entry;
432    uintptr_t tag     = (uintptr_t)&ozone->show_thumbnail_bar;
433 
434    entry.duration    = ANIMATION_CURSOR_DURATION;
435    entry.easing_enum = EASING_OUT_QUAD;
436    entry.tag         = tag;
437    entry.subject     = &ozone->animations.thumbnail_bar_position;
438 
439    gfx_animation_kill_by_tag(&tag);
440 
441    /* Show it
442     * > We only want to trigger a 'show' animation
443     *   if 'show_thumbnail_bar' is currently false.
444     *   However: 'show_thumbnail_bar' is only set
445     *   to false by the 'ozone_thumbnail_bar_hide_end'
446     *   callback. If the above 'gfx_animation_kill_by_tag()'
447     *   kills an existing 'hide' animation, then the
448     *   callback will not fire - so the sidebar will be
449     *   off screen, but a subsequent attempt to show it
450     *   here will fail, since 'show_thumbnail_bar' will
451     *   be a false positive. We therefore require an
452     *   additional 'pending_hide_thumbnail_bar' parameter
453     *   to track mid-animation state changes... */
454    if (is_playlist &&
455        !ozone->cursor_in_sidebar &&
456        (!ozone->show_thumbnail_bar || ozone->pending_hide_thumbnail_bar) &&
457        (ozone->depth == 1))
458    {
459       if (allow_animation)
460       {
461          ozone->show_thumbnail_bar = true;
462 
463          entry.cb                  = NULL;
464          entry.userdata            = NULL;
465          entry.target_value        = ozone->dimensions.thumbnail_bar_width;
466 
467          gfx_animation_push(&entry);
468       }
469       else
470       {
471          ozone->animations.thumbnail_bar_position = ozone->dimensions.thumbnail_bar_width;
472          ozone->show_thumbnail_bar                = true;
473       }
474 
475       ozone->pending_hide_thumbnail_bar = false;
476    }
477    /* Hide it */
478    else
479    {
480       if (allow_animation)
481       {
482          entry.cb                          = ozone_thumbnail_bar_hide_end;
483          entry.userdata                    = ozone;
484          entry.target_value                = 0.0f;
485 
486          ozone->pending_hide_thumbnail_bar = true;
487          gfx_animation_push(&entry);
488       }
489       else
490       {
491          ozone->animations.thumbnail_bar_position = 0.0f;
492          ozone_thumbnail_bar_hide_end(ozone);
493       }
494    }
495 }
496 
ozone_draw_entries(ozone_handle_t * ozone,gfx_display_t * p_disp,gfx_animation_t * p_anim,settings_t * settings,void * userdata,unsigned video_width,unsigned video_height,unsigned selection,unsigned selection_old,file_list_t * selection_buf,float alpha,float scroll_y,bool is_playlist)497 void ozone_draw_entries(
498       ozone_handle_t *ozone,
499       gfx_display_t *p_disp,
500       gfx_animation_t *p_anim,
501       settings_t *settings,
502       void *userdata,
503       unsigned video_width,
504       unsigned video_height,
505       unsigned selection,
506       unsigned selection_old,
507       file_list_t *selection_buf,
508       float alpha,
509       float scroll_y,
510       bool is_playlist)
511 {
512    uint32_t alpha_uint32;
513    size_t i;
514    float bottom_boundary;
515    unsigned video_info_height, video_info_width;
516    bool menu_show_sublabels          = settings->bools.menu_show_sublabels;
517    bool use_smooth_ticker            = settings->bools.menu_ticker_smooth;
518    enum gfx_animation_ticker_type
519       menu_ticker_type               = (enum gfx_animation_ticker_type)
520       settings->uints.menu_ticker_type;
521    bool old_list                     = selection_buf == &ozone->selection_buf_old;
522    int x_offset                      = 0;
523    size_t selection_y                = 0; /* 0 means no selection (we assume that no entry has y = 0) */
524    size_t old_selection_y            = 0;
525    int entry_padding                 = ozone_get_entries_padding(ozone, old_list);
526    float scale_factor                = ozone->last_scale_factor;
527    gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
528    size_t entries_end                = selection_buf ? selection_buf->size : 0;
529    size_t y                          = ozone->dimensions.header_height + ozone->dimensions.spacer_1px + ozone->dimensions.entry_padding_vertical;
530    float sidebar_offset              = ozone->sidebar_offset;
531    unsigned entry_width              = video_width - (unsigned) ozone->dimensions_sidebar_width - ozone->sidebar_offset - entry_padding * 2 - ozone->animations.thumbnail_bar_position;
532    unsigned button_height            = ozone->dimensions.entry_height; /* height of the button (entry minus sublabel) */
533    float invert                      = (ozone->fade_direction) ? -1 : 1;
534    float alpha_anim                  = old_list ? alpha : 1.0f - alpha;
535 
536    video_driver_get_size(&video_info_width, &video_info_height);
537 
538    bottom_boundary                   = video_info_height - ozone->dimensions.header_height - ozone->dimensions.footer_height;
539 
540    if (old_list)
541    {
542       alpha = 1.0f - alpha;
543       if (alpha != 1.0f)
544          x_offset += invert * -(alpha_anim * 120 * scale_factor); /* left */
545    }
546    else
547    {
548       if (alpha != 1.0f)
549          x_offset += invert *  (alpha_anim * 120 * scale_factor);  /* right */
550    }
551 
552    x_offset       += (int)sidebar_offset;
553    alpha_uint32    = (uint32_t)(alpha * 255.0f);
554 
555    /* Borders layer */
556    for (i = 0; i < entries_end; i++)
557    {
558       bool entry_selected     = selection == i;
559       bool entry_old_selected = selection_old == i;
560 
561       int border_start_x, border_start_y;
562 
563       ozone_node_t *node      = NULL;
564 
565       if (entry_selected && selection_y == 0)
566          selection_y = y;
567 
568       if (entry_old_selected && old_selection_y == 0)
569          old_selection_y = y;
570 
571       node                    = (ozone_node_t*)selection_buf->list[i].userdata;
572 
573       if (!node || ozone->empty_playlist)
574          goto border_iterate;
575 
576       if (y + scroll_y + node->height + 20 * scale_factor < ozone->dimensions.header_height + ozone->dimensions.entry_padding_vertical)
577          goto border_iterate;
578       else if (y + scroll_y - node->height - 20 * scale_factor > bottom_boundary)
579          goto border_iterate;
580 
581       border_start_x = (unsigned) ozone->dimensions_sidebar_width
582          + x_offset + entry_padding;
583       border_start_y = y + scroll_y;
584 
585       gfx_display_set_alpha(ozone->theme_dynamic.entries_border, alpha);
586       gfx_display_set_alpha(ozone->theme_dynamic.entries_checkmark, alpha);
587 
588       /* Borders */
589       gfx_display_draw_quad(
590             p_disp,
591             userdata,
592             video_width,
593             video_height,
594             border_start_x,
595             border_start_y,
596             entry_width,
597             ozone->dimensions.spacer_1px,
598             video_width,
599             video_height,
600             ozone->theme_dynamic.entries_border);
601       gfx_display_draw_quad(
602             p_disp,
603             userdata,
604             video_width,
605             video_height,
606             border_start_x,
607             border_start_y + button_height,
608             entry_width,
609             ozone->dimensions.spacer_1px,
610             video_width,
611             video_height,
612             ozone->theme_dynamic.entries_border);
613 
614 border_iterate:
615       if (node)
616          y += node->height;
617    }
618 
619    /* Cursor(s) layer - current */
620    if (!ozone->cursor_in_sidebar)
621       ozone_draw_cursor(
622             ozone,
623             p_disp,
624             userdata,
625             video_width,
626             video_height,
627             (unsigned) ozone->dimensions_sidebar_width
628             + x_offset + entry_padding + ozone->dimensions.spacer_3px,
629             entry_width - ozone->dimensions.spacer_5px,
630             button_height + ozone->dimensions.spacer_1px,
631             selection_y + scroll_y,
632             ozone->animations.cursor_alpha * alpha);
633 
634    /* Old*/
635    if (!ozone->cursor_in_sidebar_old)
636       ozone_draw_cursor(
637             ozone,
638             p_disp,
639             userdata,
640             video_width,
641             video_height,
642             (unsigned)ozone->dimensions_sidebar_width
643             + x_offset + entry_padding + ozone->dimensions.spacer_3px,
644             /* TODO/FIXME - undefined behavior reported by ASAN -
645              *-35.2358 is outside the range of representable values
646              of type 'unsigned int'
647              * */
648             entry_width - ozone->dimensions.spacer_5px,
649             button_height + ozone->dimensions.spacer_1px,
650             old_selection_y + scroll_y,
651             (1-ozone->animations.cursor_alpha) * alpha);
652 
653    /* Icons + text */
654    y = ozone->dimensions.header_height + ozone->dimensions.spacer_1px + ozone->dimensions.entry_padding_vertical;
655 
656    if (old_list)
657       y += ozone->old_list_offset_y;
658 
659    for (i = 0; i < entries_end; i++)
660    {
661       char rich_label[255];
662       char entry_value_ticker[255];
663       char wrapped_sublabel_str[MENU_SUBLABEL_MAX_LENGTH];
664       uintptr_t tex;
665       menu_entry_t entry;
666       gfx_animation_ctx_ticker_t ticker;
667       gfx_animation_ctx_ticker_smooth_t ticker_smooth;
668       unsigned ticker_x_offset     = 0;
669       unsigned ticker_str_width    = 0;
670       int value_x_offset           = 0;
671       static const char* const
672          ticker_spacer             = OZONE_TICKER_SPACER;
673       const char *sublabel_str     = NULL;
674       ozone_node_t *node           = NULL;
675       const char *entry_rich_label = NULL;
676       const char *entry_value      = NULL;
677       bool entry_selected          = false;
678       int text_offset              = -ozone->dimensions.entry_icon_padding - ozone->dimensions.entry_icon_size;
679       float *icon_color            = NULL;
680 
681       /* Initial ticker configuration */
682       if (use_smooth_ticker)
683       {
684          ticker_smooth.idx           = p_anim->ticker_pixel_idx;
685          ticker_smooth.font          = ozone->fonts.entries_label.font;
686          ticker_smooth.font_scale    = 1.0f;
687          ticker_smooth.type_enum     = menu_ticker_type;
688          ticker_smooth.spacer        = ticker_spacer;
689          ticker_smooth.x_offset      = &ticker_x_offset;
690          ticker_smooth.dst_str_width = &ticker_str_width;
691       }
692       else
693       {
694          ticker.idx                  = p_anim->ticker_idx;
695          ticker.type_enum            = menu_ticker_type;
696          ticker.spacer               = ticker_spacer;
697       }
698 
699       node                           = (ozone_node_t*)selection_buf->list[i].userdata;
700 
701       if (!node)
702          continue;
703 
704       if (y + scroll_y + node->height + 20 * scale_factor
705             < ozone->dimensions.header_height
706             + ozone->dimensions.entry_padding_vertical)
707       {
708          y += node->height;
709          continue;
710       }
711       else if (y + scroll_y - node->height - 20 * scale_factor
712             > bottom_boundary)
713       {
714          y += node->height;
715          continue;
716       }
717 
718       entry_selected                 = selection == i;
719 
720       MENU_ENTRY_INIT(entry);
721       entry.path_enabled             = false;
722       entry.label_enabled            = false;
723       menu_entry_get(&entry, 0, (unsigned)i, selection_buf, true);
724 
725       if (entry.enum_idx == MENU_ENUM_LABEL_CHEEVOS_PASSWORD)
726          entry_value         = entry.password_value;
727       else
728          entry_value         = entry.value;
729 
730       /* Prepare text */
731       if (!string_is_empty(entry.rich_label))
732          entry_rich_label  = entry.rich_label;
733       else
734          entry_rich_label  = entry.path;
735 
736       if (use_smooth_ticker)
737       {
738          ticker_smooth.selected    = entry_selected && !ozone->cursor_in_sidebar;
739          ticker_smooth.field_width = entry_width - entry_padding - (10 * scale_factor) - ozone->dimensions.entry_icon_padding;
740          ticker_smooth.src_str     = entry_rich_label;
741          ticker_smooth.dst_str     = rich_label;
742          ticker_smooth.dst_str_len = sizeof(rich_label);
743 
744          gfx_animation_ticker_smooth(&ticker_smooth);
745       }
746       else
747       {
748          ticker.s        = rich_label;
749          ticker.str      = entry_rich_label;
750          ticker.selected = entry_selected && !ozone->cursor_in_sidebar;
751          ticker.len      = (entry_width - entry_padding - (10 * scale_factor) - ozone->dimensions.entry_icon_padding) / ozone->fonts.entries_label.glyph_width;
752 
753          gfx_animation_ticker(&ticker);
754       }
755 
756       if (ozone->empty_playlist)
757       {
758          /* Note: This entry can never be selected, so ticker_x_offset
759           * is irrelevant here (i.e. this text will never scroll) */
760          unsigned text_width = font_driver_get_message_width(ozone->fonts.entries_label.font, rich_label, (unsigned)strlen(rich_label), 1);
761          x_offset = (video_info_width - (unsigned)
762                ozone->dimensions_sidebar_width - entry_padding * 2)
763             / 2 - text_width / 2 - 60 * scale_factor;
764          y = video_info_height / 2 - 60 * scale_factor;
765       }
766 
767       sublabel_str = entry.sublabel;
768 
769       if (menu_show_sublabels)
770       {
771          if (node->wrap && !string_is_empty(sublabel_str))
772          {
773             int sublabel_max_width = video_info_width -
774                entry_padding * 2 - ozone->dimensions.entry_icon_padding * 2;
775 
776             if (ozone->depth == 1)
777             {
778                sublabel_max_width -= (unsigned)
779                   ozone->dimensions_sidebar_width;
780 
781                if (ozone->show_thumbnail_bar)
782                   sublabel_max_width -= ozone->dimensions.thumbnail_bar_width;
783             }
784 
785             wrapped_sublabel_str[0] = '\0';
786             (ozone->word_wrap)(wrapped_sublabel_str, sizeof(wrapped_sublabel_str),
787                   sublabel_str, sublabel_max_width / ozone->fonts.entries_sublabel.glyph_width,
788                   ozone->fonts.entries_sublabel.wideglyph_width, 0);
789             sublabel_str = wrapped_sublabel_str;
790          }
791       }
792 
793       /* Icon */
794       tex = ozone_entries_icon_get_texture(ozone, entry.enum_idx, entry.type, entry_selected);
795       if (tex != ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SUBSETTING])
796       {
797          uintptr_t texture = tex;
798 
799          /* Console specific icons */
800          if (     entry.type == FILE_TYPE_RPL_ENTRY
801                && ozone->categories_selection_ptr > ozone->system_tab_end)
802          {
803             ozone_node_t *sidebar_node = (ozone_node_t*) file_list_get_userdata_at_offset(&ozone->horizontal_list, ozone->categories_selection_ptr - ozone->system_tab_end-1);
804 
805             if (!sidebar_node || !sidebar_node->content_icon)
806                texture = tex;
807             else
808                texture = sidebar_node->content_icon;
809          }
810 
811          /* Cheevos badges should not be recolored */
812          if (!(
813             (entry.type >= MENU_SETTINGS_CHEEVOS_START) &&
814             (entry.type < MENU_SETTINGS_NETPLAY_ROOMS_START)
815          ))
816             icon_color = ozone->theme_dynamic.entries_icon;
817          else
818             icon_color = ozone->pure_white;
819 
820          gfx_display_set_alpha(icon_color, alpha);
821 
822          if (dispctx)
823          {
824             if (dispctx->blend_begin)
825                dispctx->blend_begin(userdata);
826             if (dispctx->draw)
827                ozone_draw_icon(
828                      p_disp,
829                      userdata,
830                      video_width,
831                      video_height,
832                      ozone->dimensions.entry_icon_size,
833                      ozone->dimensions.entry_icon_size,
834                      texture,
835                      (unsigned)ozone->dimensions_sidebar_width
836                      + x_offset + entry_padding
837                      + ozone->dimensions.entry_icon_padding,
838                      y + scroll_y + ozone->dimensions.entry_height
839                      / 2 - ozone->dimensions.entry_icon_size / 2,
840                      video_width,
841                      video_height,
842                      0,
843                      1,
844                      icon_color);
845             if (dispctx->blend_end)
846                dispctx->blend_end(userdata);
847          }
848 
849          if (icon_color == ozone->pure_white)
850             gfx_display_set_alpha(icon_color, 1.0f);
851 
852          text_offset = 0;
853       }
854 
855       /* Draw text */
856       gfx_display_draw_text(
857             ozone->fonts.entries_label.font,
858             rich_label,
859             ticker_x_offset + text_offset + (unsigned)
860             ozone->dimensions_sidebar_width + x_offset        +
861             entry_padding + ozone->dimensions.entry_icon_size +
862             ozone->dimensions.entry_icon_padding * 2,
863             y + ozone->dimensions.entry_height / 2.0f         +
864             ozone->fonts.entries_label.line_centre_offset     +
865             scroll_y,
866             video_width,
867             video_height,
868             COLOR_TEXT_ALPHA(ozone->theme->text_rgba, alpha_uint32),
869             TEXT_ALIGN_LEFT,
870             1.0f,
871             false,
872             1.0f,
873             false);
874 
875       if (menu_show_sublabels)
876       {
877          if (!string_is_empty(sublabel_str))
878             gfx_display_draw_text(
879                   ozone->fonts.entries_sublabel.font,
880                   sublabel_str,
881                   (unsigned) ozone->dimensions_sidebar_width +
882                   x_offset + entry_padding                   +
883                   ozone->dimensions.entry_icon_padding,
884                   y + ozone->dimensions.entry_height - ozone->dimensions.spacer_1px + (node->height - ozone->dimensions.entry_height - (node->sublabel_lines * ozone->fonts.entries_sublabel.line_height))/2.0f + ozone->fonts.entries_sublabel.line_ascender + scroll_y,
885                   video_width,
886                   video_height,
887                   COLOR_TEXT_ALPHA(ozone->theme->text_sublabel_rgba,alpha_uint32),
888                   TEXT_ALIGN_LEFT,
889                   1.0f,
890                   false,
891                   1.0f,
892                   false);
893       }
894 
895       /* Value */
896       if (use_smooth_ticker)
897       {
898          ticker_smooth.selected    = entry_selected && !ozone->cursor_in_sidebar;
899          ticker_smooth.field_width = (entry_width - ozone->dimensions.entry_icon_size - ozone->dimensions.entry_icon_padding * 2 -
900                ((unsigned)utf8len(entry_rich_label) * ozone->fonts.entries_label.glyph_width));
901          ticker_smooth.src_str     = entry_value;
902          ticker_smooth.dst_str     = entry_value_ticker;
903          ticker_smooth.dst_str_len = sizeof(entry_value_ticker);
904 
905          /* Value text is right aligned, so have to offset x
906           * by the 'padding' width at the end of the ticker string... */
907          if (gfx_animation_ticker_smooth(&ticker_smooth))
908             value_x_offset = (ticker_x_offset + ticker_str_width) - ticker_smooth.field_width;
909       }
910       else
911       {
912          ticker.s        = entry_value_ticker;
913          ticker.str      = entry_value;
914          ticker.selected = entry_selected && !ozone->cursor_in_sidebar;
915          ticker.len      = (entry_width - ozone->dimensions.entry_icon_size - ozone->dimensions.entry_icon_padding * 2 -
916                ((unsigned)utf8len(entry_rich_label) * ozone->fonts.entries_label.glyph_width)) / ozone->fonts.entries_label.glyph_width;
917 
918          gfx_animation_ticker(&ticker);
919       }
920 
921       ozone_draw_entry_value(ozone,
922             p_disp,
923             userdata,
924             video_width,
925             video_height,
926             entry_value_ticker,
927             value_x_offset + (unsigned) ozone->dimensions_sidebar_width
928             + entry_padding + x_offset
929             + entry_width - ozone->dimensions.entry_icon_padding,
930             y + ozone->dimensions.entry_height / 2 + ozone->fonts.entries_label.line_centre_offset + scroll_y,
931             alpha_uint32,
932             &entry);
933 
934       y += node->height;
935    }
936 
937    /* Text layer */
938    ozone_font_flush(video_width, video_height, &ozone->fonts.entries_label);
939 
940    if (menu_show_sublabels)
941       ozone_font_flush(video_width, video_height, &ozone->fonts.entries_sublabel);
942 }
943 
ozone_draw_thumbnail_bar(ozone_handle_t * ozone,gfx_display_t * p_disp,gfx_animation_t * p_anim,settings_t * settings,void * userdata,unsigned video_width,unsigned video_height,bool libretro_running,float menu_framebuffer_opacity)944 void ozone_draw_thumbnail_bar(
945       ozone_handle_t *ozone,
946       gfx_display_t *p_disp,
947       gfx_animation_t *p_anim,
948       settings_t *settings,
949       void *userdata,
950       unsigned video_width,
951       unsigned video_height,
952       bool libretro_running,
953       float menu_framebuffer_opacity)
954 {
955    enum gfx_thumbnail_alignment right_thumbnail_alignment;
956    enum gfx_thumbnail_alignment left_thumbnail_alignment;
957    unsigned sidebar_width            = ozone->dimensions.thumbnail_bar_width;
958    unsigned thumbnail_width          = sidebar_width - (ozone->dimensions.sidebar_entry_icon_padding * 2);
959    int right_thumbnail_y_position    = 0;
960    int left_thumbnail_y_position     = 0;
961    int bottom_row_y_position         = 0;
962    bool show_right_thumbnail         = false;
963    bool show_left_thumbnail          = false;
964    unsigned sidebar_height           = video_height - ozone->dimensions.header_height - ozone->dimensions.sidebar_gradient_height * 2 - ozone->dimensions.footer_height;
965    unsigned x_position               = video_width - (unsigned) ozone->animations.thumbnail_bar_position;
966    int thumbnail_x_position          = x_position + ozone->dimensions.sidebar_entry_icon_padding;
967    unsigned thumbnail_height         = (video_height - ozone->dimensions.header_height - ozone->dimensions.spacer_2px - ozone->dimensions.footer_height - (ozone->dimensions.sidebar_entry_icon_padding * 3)) / 2;
968    float scale_factor                = ozone->last_scale_factor;
969    gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
970 
971    /* Background */
972    if (!libretro_running || (menu_framebuffer_opacity >= 1.0f))
973    {
974       gfx_display_draw_quad(
975             p_disp,
976             userdata,
977             video_width,
978             video_height,
979             x_position,
980             ozone->dimensions.header_height + ozone->dimensions.spacer_1px,
981             (unsigned)ozone->animations.thumbnail_bar_position,
982             ozone->dimensions.sidebar_gradient_height,
983             video_width,
984             video_height,
985             ozone->theme->sidebar_top_gradient);
986       gfx_display_draw_quad(
987             p_disp,
988             userdata,
989             video_width,
990             video_height,
991             x_position,
992             ozone->dimensions.header_height + ozone->dimensions.spacer_1px + ozone->dimensions.sidebar_gradient_height,
993             (unsigned)ozone->animations.thumbnail_bar_position,
994             sidebar_height,
995             video_width,
996             video_height,
997             ozone->theme->sidebar_background);
998       gfx_display_draw_quad(
999             p_disp,
1000             userdata,
1001             video_width,
1002             video_height,
1003             x_position,
1004             video_height - ozone->dimensions.footer_height - ozone->dimensions.sidebar_gradient_height - ozone->dimensions.spacer_1px,
1005             (unsigned) ozone->animations.thumbnail_bar_position,
1006             ozone->dimensions.sidebar_gradient_height + ozone->dimensions.spacer_1px,
1007             video_width,
1008             video_height,
1009             ozone->theme->sidebar_bottom_gradient);
1010    }
1011 
1012    /* Thumbnails */
1013    show_right_thumbnail =
1014          (ozone->thumbnails.right.status != GFX_THUMBNAIL_STATUS_MISSING) &&
1015          gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT);
1016    show_left_thumbnail  =
1017          (ozone->thumbnails.left.status != GFX_THUMBNAIL_STATUS_MISSING) &&
1018          gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT) &&
1019          !ozone->selection_core_is_viewer;
1020 
1021    /* If this entry is associated with the image viewer
1022     * and no right thumbnail is available, show a centred
1023     * message and return immediately */
1024    if (ozone->selection_core_is_viewer && !show_right_thumbnail)
1025    {
1026       ozone_draw_no_thumbnail_available(
1027             ozone,
1028             p_disp,
1029             userdata,
1030             video_width,
1031             video_height,
1032             x_position, sidebar_width, 0);
1033       return;
1034    }
1035 
1036    /* Top row
1037     * > Displays one item, with the following order
1038     *   of preference:
1039     *   1) Right thumbnail, if available
1040     *   2) Left thumbnail, if available
1041     *   3) 'No thumbnail available' message */
1042 
1043    /* > If this entry is associated with the image viewer
1044     *   core, there can be only one thumbnail and no
1045     *   content metadata -> centre image vertically */
1046    if (ozone->selection_core_is_viewer)
1047    {
1048       right_thumbnail_y_position =
1049             ozone->dimensions.header_height +
1050             ((thumbnail_height / 2) +
1051             (int)(1.5f * (float)ozone->dimensions.sidebar_entry_icon_padding));
1052 
1053       right_thumbnail_alignment = GFX_THUMBNAIL_ALIGN_CENTRE;
1054    }
1055    else
1056    {
1057       right_thumbnail_y_position =
1058             ozone->dimensions.header_height + ozone->dimensions.spacer_1px +
1059             ozone->dimensions.sidebar_entry_icon_padding;
1060 
1061       right_thumbnail_alignment = GFX_THUMBNAIL_ALIGN_BOTTOM;
1062    }
1063 
1064    /* > If we have a right thumbnail, show it */
1065    if (show_right_thumbnail)
1066       gfx_thumbnail_draw(
1067             userdata,
1068             video_width,
1069             video_height,
1070             &ozone->thumbnails.right,
1071             (float)thumbnail_x_position,
1072             (float)right_thumbnail_y_position,
1073             thumbnail_width,
1074             thumbnail_height,
1075             right_thumbnail_alignment,
1076             1.0f, 1.0f, NULL);
1077    /* > If we have neither a right thumbnail nor
1078     *   a left thumbnail to show in its place,
1079     *   display 'no thumbnail available' message */
1080    else if (!show_left_thumbnail)
1081       ozone_draw_no_thumbnail_available(
1082             ozone,
1083             p_disp,
1084             userdata,
1085             video_width,
1086             video_height,
1087             x_position,
1088             sidebar_width,
1089             thumbnail_height / 2);
1090 
1091    /* Bottom row
1092     * > Displays one item, with the following order
1093     *   of preference:
1094     *   1) Left thumbnail, if available
1095     *      *and*
1096     *      right thumbnail has been placed in the top row
1097     *      *and*
1098     *      content metadata override is not enabled
1099     *   2) Content metadata */
1100 
1101    /* > Get baseline 'start' position of bottom row */
1102    bottom_row_y_position = ozone->dimensions.header_height + ozone->dimensions.spacer_1px +
1103          thumbnail_height +
1104          (ozone->dimensions.sidebar_entry_icon_padding * 2);
1105 
1106    /* > If we have a left thumbnail, show it */
1107    if (show_left_thumbnail)
1108    {
1109       float left_thumbnail_alpha;
1110 
1111       /* Normally a right thumbnail will be shown
1112        * in the top row - if so, left thumbnail
1113        * goes at the bottom */
1114       if (show_right_thumbnail)
1115       {
1116          left_thumbnail_y_position = bottom_row_y_position;
1117          left_thumbnail_alignment  = GFX_THUMBNAIL_ALIGN_TOP;
1118          /* In this case, thumbnail opacity is dependent
1119           * upon the content metadata override
1120           * > i.e. Need to handle fade in/out animations
1121           *   and set opacity to zero when override
1122           *   is fully active */
1123          left_thumbnail_alpha      = ozone->animations.left_thumbnail_alpha;
1124       }
1125       /* If right thumbnail is missing, shift left
1126        * thumbnail up to the top row */
1127       else
1128       {
1129          left_thumbnail_y_position = right_thumbnail_y_position;
1130          left_thumbnail_alignment  = right_thumbnail_alignment;
1131          /* In this case, there is no dependence on content
1132           * metadata - thumbnail is always shown at full
1133           * opacity */
1134          left_thumbnail_alpha      = 1.0f;
1135       }
1136 
1137       /* Note: This is a NOOP when alpha is zero
1138        * (i.e. no performance impact when content
1139        * metadata override is fully active) */
1140       gfx_thumbnail_draw(
1141             userdata,
1142             video_width,
1143             video_height,
1144             &ozone->thumbnails.left,
1145             (float)thumbnail_x_position,
1146             (float)left_thumbnail_y_position,
1147             thumbnail_width,
1148             thumbnail_height,
1149             left_thumbnail_alignment,
1150             left_thumbnail_alpha,
1151             1.0f, NULL);
1152    }
1153 
1154    /* > Display content metadata in the bottom
1155     *   row if:
1156     *   - This is *not* image viewer content
1157     *     *and*
1158     *   - There is no left thumbnail
1159     *     *or*
1160     *     left thumbnail has been shifted to
1161     *     the top row
1162     *     *or*
1163     *     content metadata override is enabled
1164     *     (i.e. fade in, fade out, or fully
1165     *     active) */
1166    if (!ozone->selection_core_is_viewer &&
1167        (!show_left_thumbnail || !show_right_thumbnail ||
1168         (ozone->animations.left_thumbnail_alpha < 1.0f)))
1169    {
1170       char ticker_buf[255];
1171       gfx_animation_ctx_ticker_t ticker;
1172       gfx_animation_ctx_ticker_smooth_t ticker_smooth;
1173       static const char* const ticker_spacer = OZONE_TICKER_SPACER;
1174       unsigned ticker_x_offset               = 0;
1175       bool scroll_content_metadata           = settings->bools.ozone_scroll_content_metadata;
1176       bool use_smooth_ticker                 = settings->bools.menu_ticker_smooth;
1177       enum gfx_animation_ticker_type
1178          menu_ticker_type                    = (enum gfx_animation_ticker_type)
1179                settings->uints.menu_ticker_type;
1180       bool show_entry_idx                    = settings->bools.playlist_show_entry_idx;
1181       unsigned y                             = (unsigned)bottom_row_y_position;
1182       unsigned separator_padding             = ozone->dimensions.sidebar_entry_icon_padding*2;
1183       unsigned column_x                      = x_position + separator_padding;
1184       bool metadata_override_enabled         = show_left_thumbnail &&
1185                                                show_right_thumbnail &&
1186                                                (ozone->animations.left_thumbnail_alpha < 1.0f);
1187       float metadata_alpha                   = metadata_override_enabled ?
1188             (1.0f - ozone->animations.left_thumbnail_alpha) : 1.0f;
1189       uint32_t text_color                    = COLOR_TEXT_ALPHA(
1190             ozone->theme->text_rgba, (uint32_t)(metadata_alpha * 255.0f));
1191 
1192       if (scroll_content_metadata)
1193       {
1194          /* Initial ticker configuration */
1195          if (use_smooth_ticker)
1196          {
1197             ticker_smooth.idx                = p_anim->ticker_pixel_idx;
1198             ticker_smooth.font_scale         = 1.0f;
1199             ticker_smooth.type_enum          = menu_ticker_type;
1200             ticker_smooth.spacer             = ticker_spacer;
1201             ticker_smooth.x_offset           = &ticker_x_offset;
1202             ticker_smooth.dst_str_width      = NULL;
1203 
1204             ticker_smooth.font               = ozone->fonts.footer.font;
1205             ticker_smooth.selected           = true;
1206             ticker_smooth.field_width        = sidebar_width - (separator_padding * 2);
1207             ticker_smooth.dst_str            = ticker_buf;
1208             ticker_smooth.dst_str_len        = sizeof(ticker_buf);
1209          }
1210          else
1211          {
1212             ticker.idx                       = p_anim->ticker_idx;
1213             ticker.type_enum                 = menu_ticker_type;
1214             ticker.spacer                    = ticker_spacer;
1215 
1216             ticker.selected                  = true;
1217             ticker.len                       = (sidebar_width - (separator_padding * 2)) / ozone->fonts.footer.glyph_width;
1218             ticker.s                         = ticker_buf;
1219          }
1220       }
1221 
1222       /* Content metadata */
1223 
1224       /* Separator */
1225       gfx_display_set_alpha(ozone->theme_dynamic.entries_border, metadata_alpha);
1226 
1227       gfx_display_draw_quad(
1228             p_disp,
1229             userdata,
1230             video_width,
1231             video_height,
1232             x_position + separator_padding,
1233             y,
1234             sidebar_width - separator_padding*2,
1235             ozone->dimensions.spacer_1px,
1236             video_width,
1237             video_height,
1238             ozone->theme_dynamic.entries_border);
1239 
1240       y += 18 * scale_factor;
1241 
1242       if (scroll_content_metadata)
1243       {
1244          /* Entry enumeration */
1245          if (show_entry_idx)
1246          {
1247             ticker_buf[0] = '\0';
1248 
1249             if (use_smooth_ticker)
1250             {
1251                ticker_smooth.src_str = ozone->selection_entry_enumeration;
1252                gfx_animation_ticker_smooth(&ticker_smooth);
1253             }
1254             else
1255             {
1256                ticker.str = ozone->selection_entry_enumeration;
1257                gfx_animation_ticker(&ticker);
1258             }
1259 
1260             ozone_content_metadata_line(
1261                   video_width,
1262                   video_height,
1263                   ozone,
1264                   &y,
1265                   ticker_x_offset + column_x,
1266                   ticker_buf,
1267                   text_color,
1268                   1);
1269          }
1270 
1271          /* Core association */
1272          ticker_buf[0] = '\0';
1273 
1274          if (use_smooth_ticker)
1275          {
1276             ticker_smooth.src_str = ozone->selection_core_name;
1277             gfx_animation_ticker_smooth(&ticker_smooth);
1278          }
1279          else
1280          {
1281             ticker.str = ozone->selection_core_name;
1282             gfx_animation_ticker(&ticker);
1283          }
1284 
1285          ozone_content_metadata_line(
1286                video_width,
1287                video_height,
1288                ozone,
1289                &y,
1290                ticker_x_offset + column_x,
1291                ticker_buf,
1292                text_color,
1293                1);
1294 
1295          /* Playtime
1296           * Note: It is essentially impossible for this string
1297           * to exceed the width of the sidebar, but since we
1298           * are ticker-texting everything else, we include this
1299           * by default */
1300          ticker_buf[0] = '\0';
1301 
1302          if (use_smooth_ticker)
1303          {
1304             ticker_smooth.src_str = ozone->selection_playtime;
1305             gfx_animation_ticker_smooth(&ticker_smooth);
1306          }
1307          else
1308          {
1309             ticker.str = ozone->selection_playtime;
1310             gfx_animation_ticker(&ticker);
1311          }
1312 
1313          ozone_content_metadata_line(
1314                video_width,
1315                video_height,
1316                ozone,
1317                &y,
1318                ticker_x_offset + column_x,
1319                ticker_buf,
1320                text_color,
1321                1);
1322 
1323          /* Last played */
1324          ticker_buf[0] = '\0';
1325 
1326          if (use_smooth_ticker)
1327          {
1328             ticker_smooth.src_str = ozone->selection_lastplayed;
1329             gfx_animation_ticker_smooth(&ticker_smooth);
1330          }
1331          else
1332          {
1333             ticker.str = ozone->selection_lastplayed;
1334             gfx_animation_ticker(&ticker);
1335          }
1336 
1337          ozone_content_metadata_line(
1338                video_width,
1339                video_height,
1340                ozone,
1341                &y,
1342                ticker_x_offset + column_x,
1343                ticker_buf,
1344                text_color,
1345                1);
1346       }
1347       else
1348       {
1349          /* Entry enumeration */
1350          if (show_entry_idx)
1351             ozone_content_metadata_line(
1352                   video_width,
1353                   video_height,
1354                   ozone,
1355                   &y,
1356                   column_x,
1357                   ozone->selection_entry_enumeration,
1358                   text_color,
1359                   1);
1360 
1361          /* Core association */
1362          ozone_content_metadata_line(
1363                video_width,
1364                video_height,
1365                ozone,
1366                &y,
1367                column_x,
1368                ozone->selection_core_name,
1369                text_color,
1370                ozone->selection_core_name_lines);
1371 
1372          /* Playtime */
1373          ozone_content_metadata_line(
1374                video_width,
1375                video_height,
1376                ozone,
1377                &y,
1378                column_x,
1379                ozone->selection_playtime,
1380                text_color,
1381                1);
1382 
1383          /* Last played */
1384          ozone_content_metadata_line(
1385                video_width,
1386                video_height,
1387                ozone,
1388                &y,
1389                column_x,
1390                ozone->selection_lastplayed,
1391                text_color,
1392                ozone->selection_lastplayed_lines);
1393       }
1394 
1395       /* If metadata override is active, display an
1396        * icon to notify that a left thumbnail image
1397        * is available */
1398       if (metadata_override_enabled)
1399       {
1400          float         *col = ozone->theme_dynamic.entries_icon;
1401          /* Icon should be small and unobtrusive
1402           * > Make it 80% of the normal entry icon size */
1403          unsigned icon_size = (unsigned)((float)ozone->dimensions.sidebar_entry_icon_size * 0.8f);
1404 
1405          /* > Set its opacity to a maximum of 80% */
1406          gfx_display_set_alpha(ozone->theme_dynamic.entries_icon, metadata_alpha * 0.8f);
1407 
1408          if (dispctx)
1409          {
1410             /* Draw icon in the bottom right corner of
1411              * the thumbnail bar */
1412             if (dispctx->blend_begin)
1413                dispctx->blend_begin(userdata);
1414             if (dispctx->draw)
1415                ozone_draw_icon(
1416                      p_disp,
1417                      userdata,
1418                      video_width,
1419                      video_height,
1420                      icon_size,
1421                      icon_size,
1422                      ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_IMAGE],
1423                      x_position + sidebar_width - separator_padding - icon_size,
1424                      video_height - ozone->dimensions.footer_height - ozone->dimensions.sidebar_entry_icon_padding - icon_size,
1425                      video_width,
1426                      video_height,
1427                      0, 1, col);
1428             if (dispctx->blend_end)
1429                dispctx->blend_end(userdata);
1430          }
1431       }
1432    }
1433 }
1434