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  *  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_theme.h"
23 #include "ozone_display.h"
24 #include "ozone_sidebar.h"
25 
26 #include <string/stdstring.h>
27 #include <file/file_path.h>
28 #include <formats/image.h>
29 
30 #include "../../../gfx/gfx_animation.h"
31 
32 #include "../../../configuration.h"
33 
34 static const enum msg_hash_enums ozone_system_tabs_value[OZONE_SYSTEM_TAB_LAST] = {
35    MENU_ENUM_LABEL_VALUE_MAIN_MENU,
36    MENU_ENUM_LABEL_VALUE_SETTINGS_TAB,
37    MENU_ENUM_LABEL_VALUE_HISTORY_TAB,
38    MENU_ENUM_LABEL_VALUE_FAVORITES_TAB,
39    MENU_ENUM_LABEL_VALUE_MUSIC_TAB,
40 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
41    MENU_ENUM_LABEL_VALUE_VIDEO_TAB,
42 #endif
43 #ifdef HAVE_IMAGEVIEWER
44    MENU_ENUM_LABEL_VALUE_IMAGES_TAB,
45 #endif
46 #ifdef HAVE_NETWORKING
47    MENU_ENUM_LABEL_VALUE_NETPLAY_TAB,
48 #endif
49 #ifdef HAVE_LIBRETRODB
50    MENU_ENUM_LABEL_VALUE_ADD_TAB,
51    MENU_ENUM_LABEL_VALUE_EXPLORE_TAB
52 #else
53    MENU_ENUM_LABEL_VALUE_ADD_TAB
54 #endif
55 };
56 
57 static const enum menu_settings_type ozone_system_tabs_type[OZONE_SYSTEM_TAB_LAST] = {
58    MENU_SETTINGS,
59    MENU_SETTINGS_TAB,
60    MENU_HISTORY_TAB,
61    MENU_FAVORITES_TAB,
62    MENU_MUSIC_TAB,
63 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
64    MENU_VIDEO_TAB,
65 #endif
66 #ifdef HAVE_IMAGEVIEWER
67    MENU_IMAGES_TAB,
68 #endif
69 #ifdef HAVE_NETWORKING
70    MENU_NETPLAY_TAB,
71 #endif
72 #ifdef HAVE_LIBRETRODB
73    MENU_ADD_TAB,
74    MENU_EXPLORE_TAB
75 #else
76    MENU_ADD_TAB
77 #endif
78 };
79 
80 static const enum msg_hash_enums ozone_system_tabs_idx[OZONE_SYSTEM_TAB_LAST] = {
81    MENU_ENUM_LABEL_MAIN_MENU,
82    MENU_ENUM_LABEL_SETTINGS_TAB,
83    MENU_ENUM_LABEL_HISTORY_TAB,
84    MENU_ENUM_LABEL_FAVORITES_TAB,
85    MENU_ENUM_LABEL_MUSIC_TAB,
86 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
87    MENU_ENUM_LABEL_VIDEO_TAB,
88 #endif
89 #ifdef HAVE_IMAGEVIEWER
90    MENU_ENUM_LABEL_IMAGES_TAB,
91 #endif
92 #ifdef HAVE_NETWORKING
93    MENU_ENUM_LABEL_NETPLAY_TAB,
94 #endif
95 #ifdef HAVE_LIBRETRODB
96    MENU_ENUM_LABEL_ADD_TAB,
97    MENU_ENUM_LABEL_EXPLORE_TAB
98 #else
99    MENU_ENUM_LABEL_ADD_TAB
100 #endif
101 };
102 
103 static const unsigned ozone_system_tabs_icons[OZONE_SYSTEM_TAB_LAST] = {
104    OZONE_TAB_TEXTURE_MAIN_MENU,
105    OZONE_TAB_TEXTURE_SETTINGS,
106    OZONE_TAB_TEXTURE_HISTORY,
107    OZONE_TAB_TEXTURE_FAVORITES,
108    OZONE_TAB_TEXTURE_MUSIC,
109 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
110    OZONE_TAB_TEXTURE_VIDEO,
111 #endif
112 #ifdef HAVE_IMAGEVIEWER
113    OZONE_TAB_TEXTURE_IMAGE,
114 #endif
115 #ifdef HAVE_NETWORKING
116    OZONE_TAB_TEXTURE_NETWORK,
117 #endif
118    OZONE_TAB_TEXTURE_SCAN_CONTENT
119 };
120 
ozone_sidebar_collapse_end(void * userdata)121 static void ozone_sidebar_collapse_end(void *userdata)
122 {
123    ozone_handle_t *ozone    = (ozone_handle_t*)userdata;
124 
125    ozone->sidebar_collapsed = true;
126 }
127 
ozone_sidebar_get_scroll_y(ozone_handle_t * ozone,unsigned video_height)128 static float ozone_sidebar_get_scroll_y(
129       ozone_handle_t *ozone, unsigned video_height)
130 {
131    float scroll_y                          =
132       ozone->animations.scroll_y_sidebar;
133    float selected_position_y               =
134       ozone_get_selected_sidebar_y_position(ozone);
135    float current_selection_middle_onscreen =
136         ozone->dimensions.header_height
137       + ozone->dimensions.spacer_1px
138       + ozone->animations.scroll_y_sidebar
139       + selected_position_y
140       + ozone->dimensions.sidebar_entry_height / 2.0f;
141    float bottom_boundary                   =
142       (float)video_height
143       - (ozone->dimensions.header_height + ozone->dimensions.spacer_1px)
144       - ozone->dimensions.footer_height;
145    float entries_middle                    = (float)video_height / 2.0f;
146    float entries_height                    = ozone_get_sidebar_height(ozone);
147 
148    if (current_selection_middle_onscreen != entries_middle)
149       scroll_y = ozone->animations.scroll_y_sidebar - (current_selection_middle_onscreen - entries_middle);
150 
151    if (scroll_y + entries_height < bottom_boundary)
152       scroll_y = bottom_boundary - entries_height - ozone->dimensions.sidebar_padding_vertical;
153 
154    if (scroll_y > 0.0f)
155       return 0.0f;
156    return scroll_y;
157 }
158 
ozone_draw_sidebar(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)159 void ozone_draw_sidebar(
160       ozone_handle_t *ozone,
161       gfx_display_t *p_disp,
162       gfx_animation_t *p_anim,
163       settings_t *settings,
164       void *userdata,
165       unsigned video_width,
166       unsigned video_height,
167       bool libretro_running,
168       float menu_framebuffer_opacity
169       )
170 {
171    size_t y;
172    int entry_width;
173    char console_title[255];
174    unsigned i, sidebar_height;
175    gfx_animation_ctx_ticker_t ticker;
176    gfx_animation_ctx_ticker_smooth_t ticker_smooth;
177    static const char* const
178       ticker_spacer                  = OZONE_TICKER_SPACER;
179    unsigned ticker_x_offset          = 0;
180    uint32_t text_alpha               = ozone->animations.sidebar_text_alpha
181       * 255.0f;
182    bool use_smooth_ticker            = settings->bools.menu_ticker_smooth;
183    float scale_factor                = ozone->last_scale_factor;
184    enum gfx_animation_ticker_type
185       menu_ticker_type               = (enum gfx_animation_ticker_type)
186       settings->uints.menu_ticker_type;
187    unsigned selection_y              = 0;
188    unsigned selection_old_y          = 0;
189    unsigned horizontal_list_size     = 0;
190    gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
191 
192    /* Initial ticker configuration */
193    if (use_smooth_ticker)
194    {
195       ticker_smooth.idx           = p_anim->ticker_pixel_idx;
196       ticker_smooth.font          = ozone->fonts.sidebar.font;
197       ticker_smooth.font_scale    = 1.0f;
198       ticker_smooth.type_enum     = menu_ticker_type;
199       ticker_smooth.spacer        = ticker_spacer;
200       ticker_smooth.x_offset      = &ticker_x_offset;
201       ticker_smooth.dst_str_width = NULL;
202    }
203    else
204    {
205       ticker.idx                  = p_anim->ticker_idx;
206       ticker.type_enum            = menu_ticker_type;
207       ticker.spacer               = ticker_spacer;
208    }
209 
210    horizontal_list_size           = (unsigned)ozone->horizontal_list.size;
211 
212    gfx_display_scissor_begin(
213          p_disp,
214          userdata,
215          video_width, video_height,
216          0,
217          ozone->dimensions.header_height + ozone->dimensions.spacer_1px,
218          (unsigned) ozone->dimensions_sidebar_width,
219          video_height - ozone->dimensions.header_height - ozone->dimensions.footer_height - ozone->dimensions.spacer_1px);
220 
221    /* Background */
222    sidebar_height = video_height - ozone->dimensions.header_height - ozone->dimensions.sidebar_gradient_height * 2 - ozone->dimensions.footer_height;
223 
224    if (!libretro_running || (menu_framebuffer_opacity >= 1.0f))
225    {
226       gfx_display_draw_quad(
227             p_disp,
228             userdata,
229             video_width,
230             video_height,
231             ozone->sidebar_offset,
232             ozone->dimensions.header_height + ozone->dimensions.spacer_1px,
233             (unsigned)ozone->dimensions_sidebar_width,
234             ozone->dimensions.sidebar_gradient_height,
235             video_width,
236             video_height,
237             ozone->theme->sidebar_top_gradient);
238       gfx_display_draw_quad(
239             p_disp,
240             userdata,
241             video_width,
242             video_height,
243             ozone->sidebar_offset,
244             ozone->dimensions.header_height + ozone->dimensions.spacer_1px + ozone->dimensions.sidebar_gradient_height,
245             (unsigned)ozone->dimensions_sidebar_width,
246             sidebar_height,
247             video_width,
248             video_height,
249             ozone->theme->sidebar_background);
250       gfx_display_draw_quad(
251             p_disp,
252             userdata,
253             video_width,
254             video_height,
255             ozone->sidebar_offset,
256               video_height
257             - ozone->dimensions.footer_height
258             - ozone->dimensions.sidebar_gradient_height
259             - ozone->dimensions.spacer_1px,
260             (unsigned)ozone->dimensions_sidebar_width,
261               ozone->dimensions.sidebar_gradient_height
262             + ozone->dimensions.spacer_1px,
263             video_width,
264             video_height,
265             ozone->theme->sidebar_bottom_gradient);
266    }
267 
268    /* Tabs */
269    /* y offset computation */
270    y = ozone->dimensions.header_height + ozone->dimensions.spacer_1px + ozone->dimensions.sidebar_padding_vertical;
271    for (i = 0; i < ozone->system_tab_end + horizontal_list_size + 1; i++)
272    {
273       if (i == ozone->categories_selection_ptr)
274       {
275          selection_y = (unsigned)y;
276          if (ozone->categories_selection_ptr > ozone->system_tab_end)
277             selection_y += ozone->dimensions.sidebar_entry_padding_vertical + ozone->dimensions.spacer_1px;
278       }
279 
280       if (i == ozone->categories_active_idx_old)
281       {
282          selection_old_y = (unsigned)y;
283          if (ozone->categories_active_idx_old > ozone->system_tab_end)
284             selection_old_y += ozone->dimensions.sidebar_entry_padding_vertical + ozone->dimensions.spacer_1px;
285       }
286 
287       y += ozone->dimensions.sidebar_entry_height + ozone->dimensions.sidebar_entry_padding_vertical;
288    }
289 
290    entry_width = (unsigned) ozone->dimensions_sidebar_width - ozone->dimensions.sidebar_padding_horizontal * 2;
291 
292    /* Cursor */
293    if (ozone->cursor_in_sidebar)
294       ozone_draw_cursor(
295             ozone,
296             p_disp,
297             userdata,
298             video_width,
299             video_height,
300             ozone->sidebar_offset + ozone->dimensions.sidebar_padding_horizontal + ozone->dimensions.spacer_3px,
301             entry_width - ozone->dimensions.spacer_5px,
302             ozone->dimensions.sidebar_entry_height + ozone->dimensions.spacer_1px,
303             selection_y + ozone->animations.scroll_y_sidebar,
304             ozone->animations.cursor_alpha);
305 
306    if (ozone->cursor_in_sidebar_old)
307       ozone_draw_cursor(
308             ozone,
309             p_disp,
310             userdata,
311             video_width,
312             video_height,
313             ozone->sidebar_offset + ozone->dimensions.sidebar_padding_horizontal + ozone->dimensions.spacer_3px,
314             entry_width - ozone->dimensions.spacer_5px,
315             ozone->dimensions.sidebar_entry_height + ozone->dimensions.spacer_1px,
316             selection_old_y + ozone->animations.scroll_y_sidebar,
317             1-ozone->animations.cursor_alpha);
318 
319    /* Menu tabs */
320    y = ozone->dimensions.header_height + ozone->dimensions.spacer_1px + ozone->dimensions.sidebar_padding_vertical;
321    if (dispctx && dispctx->blend_begin)
322       dispctx->blend_begin(userdata);
323 
324    for (i = 0; i < (unsigned)(ozone->system_tab_end+1); i++)
325    {
326       enum msg_hash_enums value_idx;
327       const char *title    = NULL;
328       bool     selected    = (ozone->categories_selection_ptr == i);
329       unsigned     icon    = ozone_system_tabs_icons[ozone->tabs[i]];
330 
331       uint32_t text_color  = COLOR_TEXT_ALPHA((selected ? ozone->theme->text_selected_rgba : ozone->theme->text_rgba), text_alpha);
332       float *col           = (selected ? ozone->theme->text_selected : ozone->theme->entries_icon);
333 
334       if (!col)
335          col               = ozone->pure_white;
336 
337       /* Icon */
338       ozone_draw_icon(
339             p_disp,
340             userdata,
341             video_width,
342             video_height,
343             ozone->dimensions.sidebar_entry_icon_size,
344             ozone->dimensions.sidebar_entry_icon_size,
345             ozone->tab_textures[icon],
346             ozone->sidebar_offset + ozone->dimensions.sidebar_padding_horizontal + ozone->dimensions.sidebar_entry_icon_padding,
347             y + ozone->dimensions.sidebar_entry_height / 2 - ozone->dimensions.sidebar_entry_icon_size / 2 + ozone->animations.scroll_y_sidebar,
348             video_width,
349             video_height,
350             0,
351             1,
352             col);
353 
354       value_idx = ozone_system_tabs_value[ozone->tabs[i]];
355       title     = msg_hash_to_str(value_idx);
356 
357       /* Text */
358       if (!ozone->sidebar_collapsed)
359          gfx_display_draw_text(
360                ozone->fonts.sidebar.font,
361                title,
362                ozone->sidebar_offset
363                + ozone->dimensions.sidebar_padding_horizontal
364                + ozone->dimensions.sidebar_entry_icon_padding * 2
365                + ozone->dimensions.sidebar_entry_icon_size,
366                y + ozone->dimensions.sidebar_entry_height / 2.0f
367                + ozone->fonts.sidebar.line_centre_offset
368                + ozone->animations.scroll_y_sidebar,
369                video_width,
370                video_height,
371                text_color,
372                TEXT_ALIGN_LEFT,
373                1.0f,
374                false,
375                1.0f,
376                true);
377 
378       y += ozone->dimensions.sidebar_entry_height + ozone->dimensions.sidebar_entry_padding_vertical;
379    }
380 
381    if (dispctx && dispctx->blend_end)
382       dispctx->blend_end(userdata);
383 
384    /* Console tabs */
385    if (horizontal_list_size > 0)
386    {
387       gfx_display_draw_quad(
388             p_disp,
389             userdata,
390             video_width,
391             video_height,
392             ozone->sidebar_offset + ozone->dimensions.sidebar_padding_horizontal,
393             y + ozone->animations.scroll_y_sidebar,
394             entry_width,
395             ozone->dimensions.spacer_1px,
396             video_width,
397             video_height,
398             ozone->theme->entries_border);
399 
400       y += ozone->dimensions.sidebar_entry_padding_vertical + ozone->dimensions.spacer_1px;
401 
402       if (dispctx && dispctx->blend_begin)
403          dispctx->blend_begin(userdata);
404 
405       for (i = 0; i < horizontal_list_size; i++)
406       {
407          bool selected = (ozone->categories_selection_ptr == ozone->system_tab_end + 1 + i);
408 
409          uint32_t text_color  = COLOR_TEXT_ALPHA((selected ? ozone->theme->text_selected_rgba : ozone->theme->text_rgba), text_alpha);
410 
411          ozone_node_t *node = (ozone_node_t*)ozone->horizontal_list.list[i].userdata;
412          float *col         = (selected ? ozone->theme->text_selected : ozone->theme->entries_icon);
413 
414          if (!node)
415             goto console_iterate;
416 
417          if (!col)
418             col             = ozone->pure_white;
419 
420          /* Icon */
421          ozone_draw_icon(
422                p_disp,
423                userdata,
424                video_width,
425                video_height,
426                ozone->dimensions.sidebar_entry_icon_size,
427                ozone->dimensions.sidebar_entry_icon_size,
428                node->icon,
429                ozone->sidebar_offset + ozone->dimensions.sidebar_padding_horizontal + ozone->dimensions.sidebar_entry_icon_padding,
430                y + ozone->dimensions.sidebar_entry_height / 2 - ozone->dimensions.sidebar_entry_icon_size / 2 + ozone->animations.scroll_y_sidebar,
431                video_width,
432                video_height,
433                0,
434                1,
435                col);
436 
437          /* Text */
438          if (ozone->sidebar_collapsed)
439             goto console_iterate;
440 
441          if (use_smooth_ticker)
442          {
443             ticker_smooth.selected    = selected;
444             /* TODO/FIXME - undefined behavior reported by ASAN -
445              *-12.549 is outside the range of representable values
446              of type 'unsigned int'
447              * */
448             ticker_smooth.field_width = (entry_width - ozone->dimensions.sidebar_entry_icon_size - 40 * scale_factor);
449             ticker_smooth.src_str     = node->console_name;
450             ticker_smooth.dst_str     = console_title;
451             ticker_smooth.dst_str_len = sizeof(console_title);
452 
453             gfx_animation_ticker_smooth(&ticker_smooth);
454          }
455          else
456          {
457             ticker.len      = (entry_width - ozone->dimensions.sidebar_entry_icon_size - 40 * scale_factor) / ozone->fonts.sidebar.glyph_width;
458             ticker.s        = console_title;
459             ticker.selected = selected;
460             ticker.str      = node->console_name;
461 
462             gfx_animation_ticker(&ticker);
463          }
464 
465          gfx_display_draw_text(
466                ozone->fonts.sidebar.font,
467                console_title,
468                ticker_x_offset + ozone->sidebar_offset
469                + ozone->dimensions.sidebar_padding_horizontal
470                + ozone->dimensions.sidebar_entry_icon_padding * 2
471                + ozone->dimensions.sidebar_entry_icon_size,
472                y + ozone->dimensions.sidebar_entry_height / 2
473                + ozone->fonts.sidebar.line_centre_offset
474                + ozone->animations.scroll_y_sidebar,
475                video_width,
476                video_height,
477                text_color,
478                TEXT_ALIGN_LEFT,
479                1.0f,
480                false,
481                1.0f,
482                true);
483 
484 console_iterate:
485          y += ozone->dimensions.sidebar_entry_height + ozone->dimensions.sidebar_entry_padding_vertical;
486       }
487 
488       if (dispctx && dispctx->blend_end)
489          dispctx->blend_end(userdata);
490    }
491 
492    ozone_font_flush(video_width, video_height, &ozone->fonts.sidebar);
493 
494    if (dispctx && dispctx->scissor_end)
495       dispctx->scissor_end(userdata,
496             video_width, video_height);
497 }
498 
ozone_go_to_sidebar(ozone_handle_t * ozone,settings_t * settings,uintptr_t tag)499 void ozone_go_to_sidebar(ozone_handle_t *ozone, settings_t *settings,
500       uintptr_t tag)
501 {
502    struct gfx_animation_ctx_entry entry;
503 
504    ozone->selection_old           = ozone->selection;
505    ozone->cursor_in_sidebar_old   = ozone->cursor_in_sidebar;
506    ozone->cursor_in_sidebar       = true;
507 
508    /* Cursor animation */
509    ozone->animations.cursor_alpha = 0.0f;
510 
511    entry.cb                       = NULL;
512    entry.duration                 = ANIMATION_CURSOR_DURATION;
513    entry.easing_enum              = EASING_OUT_QUAD;
514    entry.subject                  = &ozone->animations.cursor_alpha;
515    entry.tag                      = tag;
516    entry.target_value             = 1.0f;
517    entry.userdata                 = NULL;
518 
519    gfx_animation_push(&entry);
520 
521    ozone_sidebar_update_collapse(ozone, settings, true);
522 }
523 
ozone_leave_sidebar(ozone_handle_t * ozone,settings_t * settings,uintptr_t tag)524 void ozone_leave_sidebar(ozone_handle_t *ozone,
525       settings_t *settings,
526       uintptr_t tag)
527 {
528    struct gfx_animation_ctx_entry entry;
529 
530    if (ozone->empty_playlist)
531       return;
532 
533    ozone_update_content_metadata(ozone);
534 
535    ozone->categories_active_idx_old = ozone->categories_selection_ptr;
536    ozone->cursor_in_sidebar_old     = ozone->cursor_in_sidebar;
537    ozone->cursor_in_sidebar         = false;
538 
539    /* Cursor animation */
540    ozone->animations.cursor_alpha   = 0.0f;
541 
542    entry.cb             = NULL;
543    entry.duration       = ANIMATION_CURSOR_DURATION;
544    entry.easing_enum    = EASING_OUT_QUAD;
545    entry.subject        = &ozone->animations.cursor_alpha;
546    entry.tag            = tag;
547    entry.target_value   = 1.0f;
548    entry.userdata       = NULL;
549 
550    gfx_animation_push(&entry);
551 
552    ozone_sidebar_update_collapse(ozone, settings, true);
553 }
554 
ozone_get_selected_sidebar_y_position(ozone_handle_t * ozone)555 unsigned ozone_get_selected_sidebar_y_position(ozone_handle_t *ozone)
556 {
557    return ozone->categories_selection_ptr * ozone->dimensions.sidebar_entry_height +
558          (ozone->categories_selection_ptr - 1) * ozone->dimensions.sidebar_entry_padding_vertical + ozone->dimensions.sidebar_padding_vertical +
559          (ozone->categories_selection_ptr > ozone->system_tab_end ? ozone->dimensions.sidebar_entry_padding_vertical + ozone->dimensions.spacer_1px : 0);
560 }
561 
ozone_get_sidebar_height(ozone_handle_t * ozone)562 unsigned ozone_get_sidebar_height(ozone_handle_t *ozone)
563 {
564    int entries = (int)(ozone->system_tab_end + 1 + (ozone->horizontal_list.size ));
565    return entries * ozone->dimensions.sidebar_entry_height + (entries - 1) * ozone->dimensions.sidebar_entry_padding_vertical + ozone->dimensions.sidebar_padding_vertical +
566          (ozone->horizontal_list.size > 0 ? ozone->dimensions.sidebar_entry_padding_vertical + ozone->dimensions.spacer_1px : 0);
567 }
568 
ozone_sidebar_update_collapse(ozone_handle_t * ozone,settings_t * settings,bool allow_animation)569 void ozone_sidebar_update_collapse(
570       ozone_handle_t *ozone,
571       settings_t *settings,
572       bool allow_animation)
573 {
574    /* Collapse sidebar if needed */
575    struct gfx_animation_ctx_entry entry;
576    bool is_playlist          = ozone_is_playlist(ozone, false);
577    uintptr_t tag             = (uintptr_t)&ozone->sidebar_collapsed;
578    bool collapse_sidebar     = settings->bools.ozone_collapse_sidebar;
579 
580    entry.easing_enum    = EASING_OUT_QUAD;
581    entry.tag            = tag;
582    entry.userdata       = ozone;
583    entry.duration       = ANIMATION_CURSOR_DURATION;
584 
585    gfx_animation_kill_by_tag(&tag);
586 
587    /* Collapse it */
588    if (collapse_sidebar || (is_playlist && !ozone->cursor_in_sidebar))
589    {
590       if (allow_animation)
591       {
592          entry.cb = ozone_sidebar_collapse_end;
593 
594          /* Text alpha */
595          entry.subject        = &ozone->animations.sidebar_text_alpha;
596          entry.target_value   = 0.0f;
597 
598          gfx_animation_push(&entry);
599 
600          /* Collapse */
601          entry.subject        = &ozone->dimensions_sidebar_width;
602          entry.target_value   = ozone->dimensions.sidebar_width_collapsed;
603 
604          gfx_animation_push(&entry);
605       }
606       else
607       {
608          ozone->animations.sidebar_text_alpha = 0.0f;
609          ozone->dimensions_sidebar_width      =
610             ozone->dimensions.sidebar_width_collapsed;
611          ozone->sidebar_collapsed             = true;
612       }
613    }
614    /* Show it */
615    else if (ozone->cursor_in_sidebar || (!is_playlist && !collapse_sidebar))
616    {
617       if (allow_animation)
618       {
619          ozone->sidebar_collapsed = false;
620 
621          entry.cb = NULL;
622 
623          /* Text alpha */
624          entry.subject        = &ozone->animations.sidebar_text_alpha;
625          entry.target_value   = 1.0f;
626 
627          gfx_animation_push(&entry);
628 
629          /* Collapse */
630          entry.subject        = &ozone->dimensions_sidebar_width;
631          entry.target_value   = ozone->dimensions.sidebar_width_normal;
632 
633          gfx_animation_push(&entry);
634       }
635       else
636       {
637          ozone->animations.sidebar_text_alpha   = 1.0f;
638          ozone->dimensions_sidebar_width        = ozone->dimensions.sidebar_width_normal;
639          ozone->sidebar_collapsed               = false;
640       }
641    }
642 
643    ozone_entries_update_thumbnail_bar(ozone, is_playlist, allow_animation);
644 }
645 
ozone_sidebar_goto(ozone_handle_t * ozone,unsigned new_selection)646 void ozone_sidebar_goto(ozone_handle_t *ozone, unsigned new_selection)
647 {
648    unsigned video_info_height;
649    struct gfx_animation_ctx_entry entry;
650    uintptr_t tag = (uintptr_t)ozone;
651 
652    video_driver_get_size(NULL, &video_info_height);
653 
654    if (ozone->categories_selection_ptr != new_selection)
655    {
656       ozone->categories_active_idx_old = ozone->categories_selection_ptr;
657       ozone->categories_selection_ptr = new_selection;
658 
659       ozone->cursor_in_sidebar_old = ozone->cursor_in_sidebar;
660 
661       gfx_animation_kill_by_tag(&tag);
662    }
663 
664    /* ozone->animations.scroll_y_sidebar will be modified
665     * > Set scroll acceleration to zero to minimise
666     *   potential conflicts */
667    menu_input_set_pointer_y_accel(0.0f);
668 
669    /* Cursor animation */
670    ozone->animations.cursor_alpha = 0.0f;
671 
672    entry.cb             = NULL;
673    entry.duration       = ANIMATION_CURSOR_DURATION;
674    entry.easing_enum    = EASING_OUT_QUAD;
675    entry.subject        = &ozone->animations.cursor_alpha;
676    entry.tag            = tag;
677    entry.target_value   = 1.0f;
678    entry.userdata       = NULL;
679 
680    gfx_animation_push(&entry);
681 
682    /* Scroll animation */
683    entry.cb           = NULL;
684    entry.duration     = ANIMATION_CURSOR_DURATION;
685    entry.easing_enum  = EASING_OUT_QUAD;
686    entry.subject      = &ozone->animations.scroll_y_sidebar;
687    entry.tag          = tag;
688    entry.target_value = ozone_sidebar_get_scroll_y(ozone, video_info_height);
689    entry.userdata     = NULL;
690 
691    gfx_animation_push(&entry);
692 
693    if (new_selection > ozone->system_tab_end)
694       ozone_change_tab(ozone, MENU_ENUM_LABEL_HORIZONTAL_MENU, MENU_SETTING_HORIZONTAL_MENU);
695    else
696       ozone_change_tab(ozone, ozone_system_tabs_idx[ozone->tabs[new_selection]], ozone_system_tabs_type[ozone->tabs[new_selection]]);
697 }
698 
ozone_refresh_sidebars(ozone_handle_t * ozone,settings_t * settings,unsigned video_height)699 void ozone_refresh_sidebars(
700       ozone_handle_t *ozone,
701       settings_t *settings,
702       unsigned video_height)
703 {
704    uintptr_t collapsed_tag              = (uintptr_t)&ozone->sidebar_collapsed;
705    uintptr_t offset_tag                 = (uintptr_t)&ozone->sidebar_offset;
706    uintptr_t thumbnail_tag              = (uintptr_t)&ozone->show_thumbnail_bar;
707    uintptr_t scroll_tag                 = (uintptr_t)ozone;
708    bool is_playlist                     = ozone_is_playlist(ozone, false);
709    bool collapse_sidebar                = settings->bools.ozone_collapse_sidebar;
710 
711    /* Kill any existing animations */
712    gfx_animation_kill_by_tag(&collapsed_tag);
713    gfx_animation_kill_by_tag(&offset_tag);
714    gfx_animation_kill_by_tag(&thumbnail_tag);
715    if (ozone->depth == 1)
716       gfx_animation_kill_by_tag(&scroll_tag);
717 
718    /* Set sidebar width */
719    if (collapse_sidebar || (is_playlist && !ozone->cursor_in_sidebar))
720    {
721       ozone->animations.sidebar_text_alpha = 0.0f;
722       ozone->dimensions_sidebar_width      = ozone->dimensions.sidebar_width_collapsed;
723       ozone->sidebar_collapsed             = true;
724    }
725    else if (ozone->cursor_in_sidebar || (!is_playlist && !collapse_sidebar))
726    {
727       ozone->animations.sidebar_text_alpha = 1.0f;
728       ozone->dimensions_sidebar_width      = ozone->dimensions.sidebar_width_normal;
729       ozone->sidebar_collapsed             = false;
730    }
731 
732    /* Set sidebar offset */
733    if (ozone->depth == 1)
734    {
735       ozone->sidebar_offset = 0.0f;
736       ozone->draw_sidebar   = true;
737    }
738    else if (ozone->depth > 1)
739    {
740       ozone->sidebar_offset = -ozone->dimensions_sidebar_width;
741       ozone->draw_sidebar   = false;
742    }
743 
744    /* Set thumbnail bar position */
745    if (is_playlist && !ozone->cursor_in_sidebar && ozone->depth == 1)
746    {
747       ozone->animations.thumbnail_bar_position = ozone->dimensions.thumbnail_bar_width;
748       ozone->show_thumbnail_bar                = true;
749    }
750    else
751    {
752       ozone->animations.thumbnail_bar_position = 0.0f;
753       ozone->show_thumbnail_bar                = false;
754    }
755    ozone->pending_hide_thumbnail_bar           = false;
756 
757    /* If sidebar is on-screen, update scroll position */
758    if (ozone->depth == 1)
759    {
760       ozone->animations.cursor_alpha     = 1.0f;
761       ozone->animations.scroll_y_sidebar = ozone_sidebar_get_scroll_y(ozone, video_height);
762    }
763 }
764 
ozone_change_tab(ozone_handle_t * ozone,enum msg_hash_enums tab,enum menu_settings_type type)765 void ozone_change_tab(ozone_handle_t *ozone,
766       enum msg_hash_enums tab,
767       enum menu_settings_type type)
768 {
769    file_list_t *menu_stack    = menu_entries_get_menu_stack_ptr(0);
770    file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0);
771    size_t stack_size          = menu_stack->size;
772 
773    if (menu_stack->list[stack_size - 1].label)
774       free(menu_stack->list[stack_size - 1].label);
775    menu_stack->list[stack_size - 1].label = NULL;
776 
777    menu_stack->list[stack_size - 1].label =
778       strdup(msg_hash_to_str(tab));
779    menu_stack->list[stack_size - 1].type =
780       type;
781 
782    ozone_list_cache(ozone, MENU_LIST_HORIZONTAL,
783          MENU_ACTION_LEFT);
784 
785    menu_driver_deferred_push_content_list(selection_buf);
786 }
787 
ozone_init_horizontal_list(ozone_handle_t * ozone,settings_t * settings)788 void ozone_init_horizontal_list(ozone_handle_t *ozone,
789       settings_t *settings)
790 {
791    menu_displaylist_info_t info;
792    size_t list_size;
793    size_t i;
794    const char *dir_playlist          = settings->paths.directory_playlist;
795    bool menu_content_show_playlists  = settings->bools.menu_content_show_playlists;
796    bool ozone_truncate_playlist_name = settings->bools.ozone_truncate_playlist_name;
797    bool ozone_sort_after_truncate    = settings->bools.ozone_sort_after_truncate_playlist_name;
798 
799    menu_displaylist_info_init(&info);
800 
801    info.list                    = &ozone->horizontal_list;
802    info.path                    = strdup(dir_playlist);
803    info.label                   = strdup(
804          msg_hash_to_str(MENU_ENUM_LABEL_PLAYLISTS_TAB));
805    info.exts                    = strdup("lpl");
806    info.type_default            = FILE_TYPE_PLAIN;
807    info.enum_idx                = MENU_ENUM_LABEL_PLAYLISTS_TAB;
808 
809    if (menu_content_show_playlists && !string_is_empty(info.path))
810    {
811       if (menu_displaylist_ctl(DISPLAYLIST_DATABASE_PLAYLISTS_HORIZONTAL,
812                &info, settings))
813          menu_displaylist_process(&info);
814    }
815 
816    menu_displaylist_info_free(&info);
817 
818    /* Loop through list and set console names */
819    list_size = ozone_list_get_size(ozone, MENU_LIST_HORIZONTAL);
820 
821    for (i = 0; i < list_size; i++)
822    {
823       char playlist_file_noext[255];
824       char *console_name        = NULL;
825       const char *playlist_file = ozone->horizontal_list.list[i].path;
826 
827       playlist_file_noext[0] = '\0';
828 
829       if (!playlist_file)
830       {
831          file_list_set_alt_at_offset(&ozone->horizontal_list, i, NULL);
832          continue;
833       }
834 
835       /* Remove extension */
836       fill_pathname_base_noext(playlist_file_noext,
837             playlist_file, sizeof(playlist_file_noext));
838 
839       console_name = playlist_file_noext;
840 
841       /* Truncate playlist names, if required
842        * > Format: "Vendor - Console"
843            Remove everything before the hyphen
844            and the subsequent space */
845       if (ozone_truncate_playlist_name)
846       {
847          bool hyphen_found = false;
848 
849          for (;;)
850          {
851             /* Check for "- " */
852             if (*console_name == '\0')
853                break;
854             else if (*console_name == '-' && *(console_name + 1) == ' ')
855             {
856                hyphen_found = true;
857                break;
858             }
859 
860             console_name++;
861          }
862 
863          if (hyphen_found)
864             console_name += 2;
865          else
866             console_name = playlist_file_noext;
867       }
868 
869       /* Assign console name to list */
870       file_list_set_alt_at_offset(&ozone->horizontal_list, i, console_name);
871    }
872 
873    /* If playlist names were truncated and option is
874     * enabled, re-sort list by console name */
875    if (ozone_truncate_playlist_name &&
876        ozone_sort_after_truncate &&
877        (list_size > 0))
878       file_list_sort_on_alt(&ozone->horizontal_list);
879 }
880 
ozone_refresh_horizontal_list(ozone_handle_t * ozone,settings_t * settings)881 void ozone_refresh_horizontal_list(ozone_handle_t *ozone,
882       settings_t *settings)
883 {
884    ozone_context_destroy_horizontal_list(ozone);
885    ozone_free_list_nodes(&ozone->horizontal_list, false);
886    file_list_deinitialize(&ozone->horizontal_list);
887 
888    menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL);
889 
890    file_list_initialize(&ozone->horizontal_list);
891    ozone_init_horizontal_list(ozone, settings);
892 
893    ozone_context_reset_horizontal_list(ozone);
894 }
895 
ozone_context_reset_horizontal_list(ozone_handle_t * ozone)896 void ozone_context_reset_horizontal_list(ozone_handle_t *ozone)
897 {
898    unsigned i;
899    size_t list_size = ozone_list_get_size(ozone, MENU_LIST_HORIZONTAL);
900 
901    for (i = 0; i < list_size; i++)
902    {
903       const char *path         = NULL;
904       const char *console_name = NULL;
905       ozone_node_t *node       = (ozone_node_t*)ozone->horizontal_list.list[i].userdata;
906 
907       if (!node)
908       {
909          node = ozone_alloc_node();
910          if (!node)
911             continue;
912       }
913 
914       if (!(path = ozone->horizontal_list.list[i].path))
915          continue;
916       if (string_ends_with_size(path, ".lpl",
917                strlen(path), STRLEN_CONST(".lpl")))
918       {
919          struct texture_image ti;
920          char sysname[PATH_MAX_LENGTH];
921          char texturepath[PATH_MAX_LENGTH];
922          char content_texturepath[PATH_MAX_LENGTH];
923          char icons_path[PATH_MAX_LENGTH];
924 
925          strlcpy(icons_path, ozone->icons_path, sizeof(icons_path));
926 
927          sysname[0] = texturepath[0] = content_texturepath[0] = '\0';
928 
929          fill_pathname_base_noext(sysname, path, sizeof(sysname));
930 
931          fill_pathname_join_concat(texturepath, icons_path, sysname,
932                ".png", sizeof(texturepath));
933 
934          /* If the playlist icon doesn't exist return default */
935 
936          if (!path_is_valid(texturepath))
937             fill_pathname_join_concat(texturepath, icons_path, "default",
938                   ".png", sizeof(texturepath));
939 
940          ti.width         = 0;
941          ti.height        = 0;
942          ti.pixels        = NULL;
943          ti.supports_rgba = video_driver_supports_rgba();
944 
945          if (image_texture_load(&ti, texturepath))
946          {
947             if (ti.pixels)
948             {
949                video_driver_texture_unload(&node->icon);
950                video_driver_texture_load(&ti,
951                      TEXTURE_FILTER_MIPMAP_LINEAR, &node->icon);
952             }
953 
954             image_texture_free(&ti);
955          }
956 
957          fill_pathname_join_delim(sysname, sysname,
958                "content.png", '-', sizeof(sysname));
959          strlcat(content_texturepath, icons_path, sizeof(content_texturepath));
960          strlcat(content_texturepath, PATH_DEFAULT_SLASH(), sizeof(content_texturepath));
961          strlcat(content_texturepath, sysname, sizeof(content_texturepath));
962 
963          /* If the content icon doesn't exist return default-content */
964          if (!path_is_valid(content_texturepath))
965          {
966             strlcat(icons_path,
967                   PATH_DEFAULT_SLASH() "default", sizeof(icons_path));
968             fill_pathname_join_delim(content_texturepath, icons_path,
969                   "content.png", '-', sizeof(content_texturepath));
970          }
971 
972          if (image_texture_load(&ti, content_texturepath))
973          {
974             if (ti.pixels)
975             {
976                video_driver_texture_unload(&node->content_icon);
977                video_driver_texture_load(&ti,
978                      TEXTURE_FILTER_MIPMAP_LINEAR, &node->content_icon);
979             }
980 
981             image_texture_free(&ti);
982          }
983 
984          /* Console name */
985          console_name = ozone->horizontal_list.list[i].alt
986             ? ozone->horizontal_list.list[i].alt
987             : ozone->horizontal_list.list[i].path;
988 
989          if (node->console_name)
990             free(node->console_name);
991 
992          /* Note: console_name will *always* be valid here,
993           * but provide a fallback to prevent NULL pointer
994           * dereferencing in case of unknown errors... */
995          if (console_name)
996             node->console_name = strdup(console_name);
997          else
998             node->console_name = strdup(path);
999       }
1000    }
1001 }
1002 
ozone_context_destroy_horizontal_list(ozone_handle_t * ozone)1003 void ozone_context_destroy_horizontal_list(ozone_handle_t *ozone)
1004 {
1005    unsigned i;
1006    size_t list_size = ozone_list_get_size(ozone, MENU_LIST_HORIZONTAL);
1007 
1008    for (i = 0; i < list_size; i++)
1009    {
1010       const char *path = NULL;
1011       ozone_node_t *node = (ozone_node_t*)ozone->horizontal_list.list[i].userdata;
1012 
1013       if (!node)
1014          continue;
1015 
1016       if (!(path = ozone->horizontal_list.list[i].path))
1017          continue;
1018       if (string_ends_with_size(path, ".lpl",
1019                strlen(path), STRLEN_CONST(".lpl")))
1020       {
1021          video_driver_texture_unload(&node->icon);
1022          video_driver_texture_unload(&node->content_icon);
1023       }
1024    }
1025 }
1026 
ozone_is_playlist(ozone_handle_t * ozone,bool depth)1027 bool ozone_is_playlist(ozone_handle_t *ozone, bool depth)
1028 {
1029    bool is_playlist;
1030 
1031    if (ozone->categories_selection_ptr > ozone->system_tab_end)
1032       is_playlist = true;
1033    else
1034    {
1035       switch (ozone->tabs[ozone->categories_selection_ptr])
1036       {
1037          case OZONE_SYSTEM_TAB_MAIN:
1038          case OZONE_SYSTEM_TAB_SETTINGS:
1039          case OZONE_SYSTEM_TAB_ADD:
1040 #ifdef HAVE_NETWORKING
1041          case OZONE_SYSTEM_TAB_NETPLAY:
1042 #endif
1043 #ifdef HAVE_LIBRETRODB
1044          case OZONE_SYSTEM_TAB_EXPLORE:
1045 #endif
1046             is_playlist = false;
1047             break;
1048          case OZONE_SYSTEM_TAB_HISTORY:
1049          case OZONE_SYSTEM_TAB_FAVORITES:
1050          case OZONE_SYSTEM_TAB_MUSIC:
1051 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
1052          case OZONE_SYSTEM_TAB_VIDEO:
1053 #endif
1054 #ifdef HAVE_IMAGEVIEWER
1055          case OZONE_SYSTEM_TAB_IMAGES:
1056 #endif
1057          default:
1058             is_playlist = true;
1059             break;
1060       }
1061    }
1062 
1063    if (depth)
1064       return is_playlist && ozone->depth == 1;
1065 
1066    return is_playlist;
1067 }
1068