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_display.h"
23 #include "ozone_theme.h"
24 #include "ozone_texture.h"
25 #include "ozone_sidebar.h"
26 
27 #if 0
28 #include "discord/discord.h"
29 #endif
30 
31 #include <file/file_path.h>
32 #include <string/stdstring.h>
33 #include <encodings/utf.h>
34 #include <features/features_cpu.h>
35 #include <formats/image.h>
36 #include <math/float_minmax.h>
37 
38 #include "../../../gfx/gfx_animation.h"
39 #include "../../../gfx/gfx_display.h"
40 #include "../../runtime_file.h"
41 
42 #include "../../input/input_osk.h"
43 
44 #include "../../../configuration.h"
45 #include "../../../content.h"
46 #include "../../../core_info.h"
47 #include "../../../verbosity.h"
48 
49 static const char *OZONE_TEXTURES_FILES[OZONE_TEXTURE_LAST] = {
50    "retroarch",
51    "cursor_border"
52 };
53 
54 static const char *OZONE_TAB_TEXTURES_FILES[OZONE_TAB_TEXTURE_LAST] = {
55    "retroarch",
56    "settings",
57    "history",
58    "favorites",
59    "music",
60    "video",
61    "image",
62    "netplay",
63    "add"
64 };
65 
ozone_set_thumbnail_content(void * data,const char * s)66 static void ozone_set_thumbnail_content(void *data, const char *s)
67 {
68    ozone_handle_t *ozone      = (ozone_handle_t*)data;
69 
70    if (!ozone)
71       return;
72 
73    if (ozone->is_playlist)
74    {
75       /* Playlist content */
76       if (string_is_empty(s))
77       {
78          size_t selection      = menu_navigation_get_selection();
79          size_t list_size      = menu_entries_get_size();
80          file_list_t *list     = menu_entries_get_selection_buf_ptr(0);
81 
82          /* Get playlist index corresponding
83           * to the selected entry */
84          if (list &&
85              (selection < list_size) &&
86              (list->list[selection].type == FILE_TYPE_RPL_ENTRY))
87          {
88             size_t playlist_index = list->list[selection].entry_idx;
89             gfx_thumbnail_set_content_playlist(ozone->thumbnail_path_data,
90                   playlist_get_cached(), playlist_index);
91          }
92          else
93             gfx_thumbnail_set_content_playlist(ozone->thumbnail_path_data,
94                   NULL, selection);
95       }
96    }
97    else if (ozone->is_db_manager_list)
98    {
99       /* Database list content */
100       if (string_is_empty(s))
101       {
102          menu_entry_t entry;
103          size_t selection         = menu_navigation_get_selection();
104 
105          MENU_ENTRY_INIT(entry);
106          entry.label_enabled      = false;
107          entry.rich_label_enabled = false;
108          entry.value_enabled      = false;
109          entry.sublabel_enabled   = false;
110          menu_entry_get(&entry, 0, selection, NULL, true);
111 
112          if (!string_is_empty(entry.path))
113             gfx_thumbnail_set_content(ozone->thumbnail_path_data, entry.path);
114       }
115    }
116    else if (string_is_equal(s, "imageviewer"))
117    {
118       /* Filebrowser image updates */
119       menu_entry_t entry;
120       size_t selection           = menu_navigation_get_selection();
121       file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0);
122       ozone_node_t *node         = (ozone_node_t*)selection_buf->list[selection].userdata;
123 
124       if (node)
125       {
126          MENU_ENTRY_INIT(entry);
127          entry.label_enabled      = false;
128          entry.rich_label_enabled = false;
129          entry.value_enabled      = false;
130          entry.sublabel_enabled   = false;
131          menu_entry_get(&entry, 0, selection, NULL, true);
132          if (!string_is_empty(entry.path) && !string_is_empty(node->fullpath))
133             gfx_thumbnail_set_content_image(ozone->thumbnail_path_data, node->fullpath, entry.path);
134       }
135    }
136    else if (!string_is_empty(s))
137    {
138       /* Annoying leftovers...
139        * This is required to ensure that thumbnails are
140        * updated correctly when navigating deeply through
141        * the sublevels of database manager lists.
142        * Showing thumbnails on database entries is a
143        * pointless nuisance and a waste of CPU cycles, IMHO... */
144       gfx_thumbnail_set_content(ozone->thumbnail_path_data, s);
145    }
146 
147    ozone_update_content_metadata(ozone);
148 }
149 
150 /* Returns true if specified category is currently
151  * displayed on screen */
ozone_category_onscreen(ozone_handle_t * ozone,size_t idx)152 static bool INLINE ozone_category_onscreen(
153       ozone_handle_t *ozone, size_t idx)
154 {
155    return (idx >= ozone->first_onscreen_category) &&
156           (idx <= ozone->last_onscreen_category);
157 }
158 
159 /* If current category is on screen, returns its
160  * index. If current category is off screen, returns
161  * index of centremost on screen category. */
ozone_get_onscreen_category_selection(ozone_handle_t * ozone)162 static size_t ozone_get_onscreen_category_selection(
163       ozone_handle_t *ozone)
164 {
165    /* Check whether selected category is already on screen */
166    if (ozone_category_onscreen(ozone, ozone->categories_selection_ptr))
167       return ozone->categories_selection_ptr;
168 
169    /* Return index of centremost category */
170    return (ozone->first_onscreen_category >> 1) +
171          (ozone->last_onscreen_category >> 1);
172 }
173 
174 /* Returns true if specified entry is currently
175  * displayed on screen */
176 /* Check whether selected item is already on screen */
177 #define OZONE_ENTRY_ONSCREEN(ozone, idx) (((idx) >= (ozone)->first_onscreen_entry) && ((idx) <= (ozone)->last_onscreen_entry))
178 
179 /* If currently selected entry is off screen,
180  * moves selection to specified on screen target
181  * > Does nothing if currently selected item is
182  *   already on screen */
ozone_auto_select_onscreen_entry(ozone_handle_t * ozone,enum ozone_onscreen_entry_position_type target_entry)183 static void ozone_auto_select_onscreen_entry(
184       ozone_handle_t *ozone,
185       enum ozone_onscreen_entry_position_type target_entry)
186 {
187    size_t selection = 0;
188 
189    /* Update selection index */
190    switch (target_entry)
191    {
192       case OZONE_ONSCREEN_ENTRY_FIRST:
193          selection = ozone->first_onscreen_entry;
194          break;
195       case OZONE_ONSCREEN_ENTRY_LAST:
196          selection = ozone->last_onscreen_entry;
197          break;
198       case OZONE_ONSCREEN_ENTRY_CENTRE:
199       default:
200          selection = (ozone->first_onscreen_entry >> 1) +
201                (ozone->last_onscreen_entry >> 1);
202          break;
203    }
204 
205    /* Apply new selection */
206    menu_navigation_set_selection(selection);
207 }
208 
ozone_metadata_override_available(ozone_handle_t * ozone)209 static bool INLINE ozone_metadata_override_available(ozone_handle_t *ozone)
210 {
211    /* Ugly construct...
212     * Content metadata display override may be
213     * toggled if the following are true:
214     * - We are viewing playlist thumbnails
215     * - This is *not* an image viewer playlist
216     * - Both right and left thumbnails are
217     *   enabled/available
218     * Short circuiting means that in most cases
219     * only 'ozone->is_playlist' will be evaluated,
220     * so this isn't too much of a performance hog... */
221    return ozone->is_playlist
222       &&  ozone->show_thumbnail_bar
223       && !ozone->selection_core_is_viewer
224       && (ozone->thumbnails.left.status != GFX_THUMBNAIL_STATUS_MISSING)
225       && gfx_thumbnail_is_enabled(ozone->thumbnail_path_data,
226             GFX_THUMBNAIL_LEFT)
227       && (ozone->thumbnails.right.status != GFX_THUMBNAIL_STATUS_MISSING)
228       && gfx_thumbnail_is_enabled(ozone->thumbnail_path_data,
229             GFX_THUMBNAIL_RIGHT);
230 }
231 
ozone_is_current_entry_settings(size_t current_selection)232 static bool ozone_is_current_entry_settings(size_t current_selection)
233 {
234    menu_entry_t last_entry;
235    const char *entry_value;
236 
237    unsigned entry_type                = 0;
238    enum msg_file_type entry_file_type = FILE_TYPE_NONE;
239 
240    MENU_ENTRY_INIT(last_entry);
241    last_entry.path_enabled       = false;
242    last_entry.label_enabled      = false;
243    last_entry.rich_label_enabled = false;
244    last_entry.sublabel_enabled   = false;
245 
246    menu_entry_get(&last_entry, 0, current_selection, NULL, true);
247 
248    if (last_entry.enum_idx == MENU_ENUM_LABEL_CHEEVOS_PASSWORD)
249       entry_value = last_entry.password_value;
250    else
251       entry_value = last_entry.value;
252 
253    entry_file_type = msg_hash_to_file_type(msg_hash_calculate(entry_value));
254    entry_type      = last_entry.type;
255 
256    /* Logic below taken from materialui_pointer_up_swipe_horz_default */
257    if (!string_is_empty(entry_value))
258    {
259       /* Toggle switch off */
260       if (string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED)) ||
261           string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF)))
262       {
263          return true;
264       }
265       /* Toggle switch on */
266       else if (string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED)) ||
267                string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON)))
268       {
269          return true;
270       }
271       /* Normal value text */
272       else
273       {
274          switch (entry_file_type)
275          {
276             case FILE_TYPE_IN_CARCHIVE:
277             case FILE_TYPE_MORE:
278             case FILE_TYPE_CORE:
279             case FILE_TYPE_DIRECT_LOAD:
280             case FILE_TYPE_RDB:
281             case FILE_TYPE_CURSOR:
282             case FILE_TYPE_PLAIN:
283             case FILE_TYPE_DIRECTORY:
284             case FILE_TYPE_MUSIC:
285             case FILE_TYPE_IMAGE:
286             case FILE_TYPE_MOVIE:
287                break;
288             case FILE_TYPE_COMPRESSED:
289                /* Note that we have to perform a backup check here,
290                 * since the 'manual content scan - file extensions'
291                 * setting may have a value of 'zip' or '7z' etc, which
292                 * means it would otherwise get incorreclty identified as
293                 * an archive file... */
294                if (entry_type != FILE_TYPE_CARCHIVE)
295                   return true;
296                break;
297             default:
298                return true;
299                break;
300          }
301       }
302    }
303 
304    return false;
305 }
306 
ozone_parse_menu_entry_action(ozone_handle_t * ozone,settings_t * settings,enum menu_action action)307 static enum menu_action ozone_parse_menu_entry_action(
308       ozone_handle_t *ozone, settings_t *settings,
309       enum menu_action action)
310 {
311    uintptr_t tag;
312    int new_selection;
313    enum menu_action new_action   = action;
314    file_list_t *selection_buf    = NULL;
315    unsigned horizontal_list_size = 0;
316    bool menu_navigation_wraparound_enable;
317    bool is_current_entry_settings;
318    size_t selection;
319    size_t selection_total;
320 
321    /* If fullscreen thumbnail view is active, any
322     * valid menu action will disable it... */
323    if (ozone->show_fullscreen_thumbnails)
324    {
325       if (action != MENU_ACTION_NOOP)
326       {
327          ozone_hide_fullscreen_thumbnails(ozone, true);
328 
329          /* ...and any action other than Select/OK
330           * is ignored
331           * > We allow pass-through of Select/OK since
332           *   users may want to run content directly
333           *   after viewing fullscreen thumbnails,
334           *   and having to press RetroPad A or the Return
335           *   key twice is navigationally confusing
336           * > Note that we can only do this for non-pointer
337           *   input
338           * > Note that we don't do this when viewing a
339           *   file list, since there is no quick menu
340           *   in this case - i.e. content loads directly,
341           *   and a sudden transition from fullscreen
342           *   thumbnail to content is jarring...
343           * > We also don't do this when viewing a database
344           *   manager list, because the menu transition
345           *   detection becomes too cumbersome... */
346          if (ozone->is_file_list ||
347              ozone->is_db_manager_list ||
348              ((action != MENU_ACTION_SELECT) &&
349               (action != MENU_ACTION_OK)))
350             return MENU_ACTION_NOOP;
351       }
352    }
353 
354    horizontal_list_size       = (unsigned)ozone->horizontal_list.size;
355 
356    ozone->messagebox_state    = menu_input_dialog_get_display_kb();
357    selection_buf              = menu_entries_get_selection_buf_ptr(0);
358    tag                        = (uintptr_t)selection_buf;
359    selection                  = menu_navigation_get_selection();
360    selection_total            = menu_entries_get_size();
361 
362    menu_navigation_wraparound_enable   = settings->bools.menu_navigation_wraparound_enable;
363 
364    /* Don't wiggle left or right if the current entry is a setting. This is
365       partially wrong because some settings don't use left and right to change their value, such as
366       free input fields (passwords...). This is good enough. */
367    is_current_entry_settings = ozone_is_current_entry_settings(selection);
368 
369    /* Scan user inputs */
370    switch (action)
371    {
372       case MENU_ACTION_START:
373          ozone->cursor_mode = false;
374          /* If this is a menu with thumbnails and cursor
375           * is not in the sidebar, attempt to show
376           * fullscreen thumbnail view */
377          if (ozone->fullscreen_thumbnails_available &&
378              !ozone->cursor_in_sidebar)
379          {
380             ozone_show_fullscreen_thumbnails(ozone);
381             new_action = MENU_ACTION_NOOP;
382          }
383          break;
384       case MENU_ACTION_DOWN:
385          if (ozone->cursor_in_sidebar)
386          {
387             /* If cursor is active, ensure we target
388              * an on screen category */
389             size_t selection = (ozone->cursor_mode)
390                ? ozone_get_onscreen_category_selection(ozone)
391                : ozone->categories_selection_ptr;
392 
393             new_selection    = (int)(selection + 1);
394 
395             if (new_selection >= (int)(ozone->system_tab_end
396                      + horizontal_list_size + 1))
397                new_selection = 0;
398 
399             ozone_sidebar_goto(ozone, new_selection);
400 
401             new_action         = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
402             ozone->cursor_mode = false;
403             break;
404          }
405          else if (!menu_navigation_wraparound_enable && selection == selection_total - 1)
406          {
407             ozone_start_cursor_wiggle(ozone, MENU_ACTION_DOWN);
408          }
409 
410          /* If pointer is active and current selection
411           * is off screen, auto select *centre* item */
412          if (ozone->cursor_mode)
413             if (!OZONE_ENTRY_ONSCREEN(ozone, selection))
414                ozone_auto_select_onscreen_entry(ozone,
415                      OZONE_ONSCREEN_ENTRY_CENTRE);
416          ozone->cursor_mode = false;
417          break;
418       case MENU_ACTION_UP:
419          if (ozone->cursor_in_sidebar)
420          {
421             /* If cursor is active, ensure we target
422              * an on screen category */
423             size_t selection   = (ozone->cursor_mode)
424                ? ozone_get_onscreen_category_selection(ozone)
425                : ozone->categories_selection_ptr;
426             new_selection      = (int)selection - 1;
427 
428             if (new_selection < 0)
429                new_selection   = horizontal_list_size + ozone->system_tab_end;
430 
431             ozone_sidebar_goto(ozone, new_selection);
432 
433             new_action         = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
434             ozone->cursor_mode = false;
435             break;
436          }
437          else if (!menu_navigation_wraparound_enable && selection == 0)
438          {
439             ozone_start_cursor_wiggle(ozone, MENU_ACTION_UP);
440          }
441 
442          /* If pointer is active and current selection
443           * is off screen, auto select *centre* item */
444          if (ozone->cursor_mode)
445             if (!OZONE_ENTRY_ONSCREEN(ozone, selection))
446                ozone_auto_select_onscreen_entry(ozone,
447                      OZONE_ONSCREEN_ENTRY_CENTRE);
448          ozone->cursor_mode = false;
449          break;
450       case MENU_ACTION_LEFT:
451          ozone->cursor_mode = false;
452 
453          if (ozone->cursor_in_sidebar)
454          {
455             new_action      = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
456             ozone_start_cursor_wiggle(ozone, MENU_ACTION_LEFT);
457 
458             break;
459          }
460          else if (ozone->depth > 1)
461          {
462             if (!menu_navigation_wraparound_enable && selection == 0 && !is_current_entry_settings)
463             {
464                /* Pressing left goes up but faster, so
465                   wiggle up to say that there is nothing more upwards
466                   even though the user pressed the left button */
467                ozone_start_cursor_wiggle(ozone, MENU_ACTION_DOWN);
468             }
469 
470             break;
471          }
472 
473          ozone_go_to_sidebar(ozone, settings, tag);
474 
475          new_action = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
476          break;
477       case MENU_ACTION_RIGHT:
478          ozone->cursor_mode = false;
479          if (!ozone->cursor_in_sidebar)
480          {
481             if (ozone->depth == 1)
482             {
483                new_action = MENU_ACTION_NOOP;
484                ozone_start_cursor_wiggle(ozone, MENU_ACTION_RIGHT);
485             }
486             else if (!menu_navigation_wraparound_enable && selection == selection_total - 1 && !is_current_entry_settings)
487             {
488                /* Pressing right goes down but faster, so
489                   wiggle down to say that there is nothing more downwards
490                   even though the user pressed the right button */
491                ozone_start_cursor_wiggle(ozone, MENU_ACTION_DOWN);
492             }
493 
494             break;
495          }
496 
497          ozone_leave_sidebar(ozone, settings, tag);
498 
499          new_action = MENU_ACTION_ACCESSIBILITY_SPEAK_LABEL;
500          break;
501       case MENU_ACTION_OK:
502          ozone->cursor_mode = false;
503          if (ozone->cursor_in_sidebar)
504          {
505             ozone_leave_sidebar(ozone, settings, tag);
506             new_action = MENU_ACTION_ACCESSIBILITY_SPEAK_LABEL;
507             break;
508          }
509          break;
510       case MENU_ACTION_CANCEL:
511          ozone->cursor_mode = false;
512 
513          /* If this is a playlist, handle 'backing out'
514           * of a search, if required */
515          if (ozone->is_playlist)
516             if (menu_entries_search_get_terms())
517                break;
518 
519          if (ozone->cursor_in_sidebar)
520          {
521             /* Go back to main menu tab */
522             if (ozone->categories_selection_ptr != 0)
523                ozone_sidebar_goto(ozone, 0);
524 
525             new_action = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
526             break;
527          }
528 
529          if (menu_entries_get_stack_size(0) == 1)
530          {
531             ozone_go_to_sidebar(ozone, settings, tag);
532             new_action = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
533          }
534          break;
535 
536       case MENU_ACTION_SCROLL_UP:
537          /* Descend alphabet (Z towards A) */
538 
539          /* Ignore if cursor is in sidebar */
540          if (ozone->cursor_in_sidebar)
541          {
542             new_action = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
543             break;
544          }
545 
546          /* If pointer is active and current selection
547           * is off screen, auto select *last* item */
548          if (ozone->cursor_mode)
549             if (!OZONE_ENTRY_ONSCREEN(ozone, selection))
550                ozone_auto_select_onscreen_entry(ozone,
551                      OZONE_ONSCREEN_ENTRY_LAST);
552          ozone->cursor_mode = false;
553          break;
554       case MENU_ACTION_SCROLL_DOWN:
555          /* Ascend alphabet (A towards Z) */
556 
557          /* > Ignore if cursor is in sidebar */
558          if (ozone->cursor_in_sidebar)
559          {
560             new_action = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
561             break;
562          }
563 
564          /* If pointer is active and current selection
565           * is off screen, auto select *first* item */
566          if (ozone->cursor_mode)
567             if (!OZONE_ENTRY_ONSCREEN(ozone, selection))
568                ozone_auto_select_onscreen_entry(ozone,
569                      OZONE_ONSCREEN_ENTRY_FIRST);
570          ozone->cursor_mode = false;
571          break;
572 
573       case MENU_ACTION_INFO:
574          /* If we currently viewing a playlist with
575           * dual thumbnails, toggle the content metadata
576           * override */
577          if (ozone_metadata_override_available(ozone))
578          {
579             ozone_toggle_metadata_override(ozone);
580             new_action = MENU_ACTION_NOOP;
581          }
582          /* ...and since the user is likely to trigger
583           * 'INFO' actions on invalid playlist entries,
584           * suppress this action entirely when viewing
585           * playlists under all other conditions
586           * > Playlists have no 'INFO' entries - the
587           *   user is just greeted with a useless
588           *   'no information available' message
589           * > It is incredibly annoying to inadvertently
590           *   trigger this message when you just want to
591           *   toggle metadata... */
592          else if (ozone->is_playlist && ozone->show_thumbnail_bar)
593             new_action = MENU_ACTION_NOOP;
594 
595          ozone->cursor_mode = false;
596          break;
597 
598       default:
599          /* In all other cases, pass through input
600           * menu action without intervention */
601          break;
602    }
603 
604    return new_action;
605 }
606 
607 
608 /* Menu entry action callback */
ozone_menu_entry_action(void * userdata,menu_entry_t * entry,size_t i,enum menu_action action)609 static int ozone_menu_entry_action(
610       void *userdata, menu_entry_t *entry,
611       size_t i, enum menu_action action)
612 {
613    menu_entry_t new_entry;
614    ozone_handle_t *ozone       = (ozone_handle_t*)userdata;
615    menu_entry_t *entry_ptr     = entry;
616    settings_t *settings        = config_get_ptr();
617    size_t selection            = i;
618    /* Process input action */
619    enum menu_action new_action = ozone_parse_menu_entry_action(ozone, settings,
620          action);
621    /* Check whether current selection has changed
622     * (due to automatic on screen entry selection...) */
623    size_t new_selection        = menu_navigation_get_selection();
624 
625    if (new_selection != selection)
626    {
627       /* Selection has changed - must update
628        * entry pointer */
629       MENU_ENTRY_INIT(new_entry);
630       new_entry.path_enabled       = false;
631       new_entry.label_enabled      = false;
632       new_entry.rich_label_enabled = false;
633       new_entry.value_enabled      = false;
634       new_entry.sublabel_enabled   = false;
635       menu_entry_get(&new_entry, 0, new_selection, NULL, true);
636       entry_ptr                    = &new_entry;
637    }
638 
639    /* Call standard generic_menu_entry_action() function */
640    return generic_menu_entry_action(userdata, entry_ptr,
641          new_selection, new_action);
642 }
643 
ozone_free_node(ozone_node_t * node)644 static void ozone_free_node(ozone_node_t *node)
645 {
646    if (!node)
647       return;
648 
649    if (node->console_name)
650       free(node->console_name);
651 
652    node->console_name = NULL;
653 
654    if (node->fullpath)
655       free(node->fullpath);
656 
657    node->fullpath = NULL;
658 
659    free(node);
660 }
661 
ozone_menu_animation_update_time(float * ticker_pixel_increment,unsigned video_width,unsigned video_height)662 static void ozone_menu_animation_update_time(
663       float *ticker_pixel_increment,
664       unsigned video_width, unsigned video_height)
665 {
666    gfx_display_t *p_disp      = disp_get_ptr();
667    settings_t *settings       = config_get_ptr();
668    /* Ozone uses DPI scaling
669     * > Smooth ticker scaling multiplier is
670     *   gfx_display_get_dpi_scale() multiplied by
671     *   a small correction factor to achieve a
672     *   default scroll speed equal to that of the
673     *   non-smooth ticker */
674    *(ticker_pixel_increment) *= gfx_display_get_dpi_scale(p_disp,
675          settings, video_width, video_height) * 0.5f;
676 }
677 
ozone_init(void ** userdata,bool video_is_threaded)678 static void *ozone_init(void **userdata, bool video_is_threaded)
679 {
680    unsigned i;
681    bool fallback_color_theme           = false;
682    unsigned width, height, color_theme = 0;
683    ozone_handle_t *ozone               = NULL;
684    settings_t *settings                = config_get_ptr();
685    gfx_animation_t *p_anim             = anim_get_ptr();
686    gfx_display_t *p_disp               = disp_get_ptr();
687    menu_handle_t *menu                 = (menu_handle_t*)calloc(1, sizeof(*menu));
688    const char *directory_assets        = settings->paths.directory_assets;
689 
690    if (!menu)
691       return NULL;
692 
693    video_driver_get_size(&width, &height);
694 
695    ozone = (ozone_handle_t*)calloc(1, sizeof(ozone_handle_t));
696 
697    if (!ozone)
698       goto error;
699 
700    *userdata = ozone;
701 
702    for (i = 0; i < 15; i++)
703       ozone->pure_white[i]  = 1.00f;
704 
705    ozone->last_width        = width;
706    ozone->last_height       = height;
707    ozone->last_scale_factor = gfx_display_get_dpi_scale(p_disp,
708          settings, width, height);
709 
710    file_list_initialize(&ozone->selection_buf_old);
711 
712    ozone->draw_sidebar                          = true;
713    ozone->sidebar_offset                        = 0;
714    ozone->pending_message                       = NULL;
715    ozone->is_playlist                           = false;
716    ozone->categories_selection_ptr              = 0;
717    ozone->pending_message                       = NULL;
718    ozone->show_cursor                           = false;
719    ozone->show_screensaver                      = false;
720 
721    ozone->first_frame                           = true;
722    ozone->cursor_mode                           = false;
723 
724    ozone->sidebar_collapsed                     = false;
725    ozone->animations.sidebar_text_alpha         = 1.0f;
726 
727    ozone->animations.thumbnail_bar_position     = 0.0f;
728    ozone->show_thumbnail_bar                    = false;
729    ozone->pending_hide_thumbnail_bar            = false;
730    ozone->dimensions_sidebar_width              = 0.0f;
731 
732    ozone->num_search_terms_old                  = 0;
733 
734    ozone->cursor_wiggle_state.wiggling          = false;
735 
736    ozone->thumbnail_path_data = gfx_thumbnail_path_init();
737    if (!ozone->thumbnail_path_data)
738       goto error;
739 
740    ozone->screensaver = menu_screensaver_init();
741    if (!ozone->screensaver)
742       goto error;
743 
744    ozone->fullscreen_thumbnails_available       = false;
745    ozone->show_fullscreen_thumbnails            = false;
746    ozone->animations.fullscreen_thumbnail_alpha = 0.0f;
747    ozone->fullscreen_thumbnail_selection        = 0;
748    ozone->fullscreen_thumbnail_label[0]         = '\0';
749 
750    ozone->animations.left_thumbnail_alpha       = 1.0f;
751    ozone->force_metadata_display                = false;
752 
753    gfx_thumbnail_set_stream_delay(-1.0f);
754    gfx_thumbnail_set_fade_duration(-1.0f);
755    gfx_thumbnail_set_fade_missing(false);
756 
757    ozone_sidebar_update_collapse(ozone, settings, false);
758 
759    ozone->system_tab_end                        = 0;
760    ozone->tabs[ozone->system_tab_end]           = OZONE_SYSTEM_TAB_MAIN;
761    if (      settings->bools.menu_content_show_settings
762          && !settings->bools.kiosk_mode_enable)
763       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_SETTINGS;
764    if (settings->bools.menu_content_show_favorites)
765       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_FAVORITES;
766    if (settings->bools.menu_content_show_history)
767       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_HISTORY;
768 #ifdef HAVE_IMAGEVIEWER
769    if (settings->bools.menu_content_show_images)
770       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_IMAGES;
771 #endif
772    if (settings->bools.menu_content_show_music)
773       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_MUSIC;
774 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
775    if (settings->bools.menu_content_show_video)
776       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_VIDEO;
777 #endif
778 #ifdef HAVE_NETWORKING
779    if (settings->bools.menu_content_show_netplay)
780       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_NETPLAY;
781 #endif
782 
783    if (      settings->bools.menu_content_show_add
784          && !settings->bools.kiosk_mode_enable)
785       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_ADD;
786 
787 #if defined(HAVE_LIBRETRODB)
788    if (settings->bools.menu_content_show_explore)
789       ozone->tabs[++ozone->system_tab_end]      = OZONE_SYSTEM_TAB_EXPLORE;
790 #endif
791 
792    menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL);
793 
794    gfx_display_set_width(width);
795    gfx_display_set_height(height);
796 
797    gfx_display_init_white_texture(gfx_display_white_texture);
798 
799    file_list_initialize(&ozone->horizontal_list);
800 
801    ozone_init_horizontal_list(ozone, settings);
802 
803    /* Theme */
804    if (settings->bools.menu_use_preferred_system_color_theme)
805    {
806 #ifdef HAVE_LIBNX
807       if (R_SUCCEEDED(setsysInitialize()))
808       {
809          ColorSetId theme;
810          setsysGetColorSetId(&theme);
811          color_theme = (theme == ColorSetId_Dark) ? 1 : 0;
812          ozone_set_color_theme(ozone, color_theme);
813          configuration_set_uint(settings,
814                settings->uints.menu_ozone_color_theme, color_theme);
815          configuration_set_bool(settings,
816                settings->bools.menu_preferred_system_color_theme_set, true);
817          setsysExit();
818       }
819       else
820 #endif
821          fallback_color_theme = true;
822    }
823    else
824       fallback_color_theme    = true;
825 
826    if (fallback_color_theme)
827    {
828       color_theme = settings->uints.menu_ozone_color_theme;
829       ozone_set_color_theme(ozone, color_theme);
830    }
831 
832    ozone->need_compute                 = false;
833    ozone->animations.scroll_y          = 0.0f;
834    ozone->animations.scroll_y_sidebar  = 0.0f;
835 
836    ozone->first_onscreen_entry         = 0;
837    ozone->last_onscreen_entry          = 0;
838    ozone->first_onscreen_category      = 0;
839    ozone->last_onscreen_category       = 0;
840 
841    /* Assets path */
842    fill_pathname_join(
843       ozone->assets_path,
844       directory_assets,
845       "ozone",
846       sizeof(ozone->assets_path)
847    );
848 
849    /* PNG path */
850    fill_pathname_join(
851       ozone->png_path,
852       ozone->assets_path,
853       "png",
854       sizeof(ozone->png_path)
855    );
856 
857    /* Sidebar path */
858    fill_pathname_join(
859       ozone->tab_path,
860       ozone->png_path,
861       "sidebar",
862       sizeof(ozone->tab_path)
863    );
864 
865    /* Icons path */
866    fill_pathname_application_special(ozone->icons_path,
867        sizeof(ozone->icons_path),
868        APPLICATION_SPECIAL_DIRECTORY_ASSETS_OZONE_ICONS);
869 
870    last_use_preferred_system_color_theme = settings->bools.menu_use_preferred_system_color_theme;
871    p_anim->updatetime_cb = ozone_menu_animation_update_time;
872 
873    /* set word_wrap function pointer */
874    ozone->word_wrap = msg_hash_get_wideglyph_str() ? word_wrap_wideglyph : word_wrap;
875 
876    return menu;
877 
878 error:
879    if (ozone)
880    {
881       ozone_free_list_nodes(&ozone->horizontal_list, false);
882       ozone_free_list_nodes(&ozone->selection_buf_old, false);
883       file_list_deinitialize(&ozone->horizontal_list);
884       file_list_deinitialize(&ozone->selection_buf_old);
885    }
886 
887    if (menu)
888       free(menu);
889 
890    return NULL;
891 }
892 
ozone_free(void * data)893 static void ozone_free(void *data)
894 {
895    ozone_handle_t *ozone   = (ozone_handle_t*) data;
896 
897    if (ozone)
898    {
899       video_coord_array_free(&ozone->fonts.footer.raster_block.carr);
900       video_coord_array_free(&ozone->fonts.title.raster_block.carr);
901       video_coord_array_free(&ozone->fonts.time.raster_block.carr);
902       video_coord_array_free(&ozone->fonts.entries_label.raster_block.carr);
903       video_coord_array_free(&ozone->fonts.entries_sublabel.raster_block.carr);
904       video_coord_array_free(&ozone->fonts.sidebar.raster_block.carr);
905 
906       ozone_free_list_nodes(&ozone->selection_buf_old, false);
907       ozone_free_list_nodes(&ozone->horizontal_list, false);
908       file_list_deinitialize(&ozone->selection_buf_old);
909       file_list_deinitialize(&ozone->horizontal_list);
910 
911       if (!string_is_empty(ozone->pending_message))
912          free(ozone->pending_message);
913 
914       if (ozone->thumbnail_path_data)
915          free(ozone->thumbnail_path_data);
916 
917       menu_screensaver_free(ozone->screensaver);
918    }
919 
920    if (gfx_display_white_texture)
921       video_driver_texture_unload(&gfx_display_white_texture);
922 
923    font_driver_bind_block(NULL, NULL);
924 }
925 
ozone_update_thumbnail_image(void * data)926 static void ozone_update_thumbnail_image(void *data)
927 {
928    ozone_handle_t *ozone             = (ozone_handle_t*)data;
929    size_t selection                  = menu_navigation_get_selection();
930    settings_t *settings              = config_get_ptr();
931    playlist_t *playlist              = playlist_get_cached();
932    unsigned gfx_thumbnail_upscale_threshold = settings->uints.gfx_thumbnail_upscale_threshold;
933    bool network_on_demand_thumbnails = settings->bools.network_on_demand_thumbnails;
934 
935    if (!ozone)
936       return;
937 
938    gfx_thumbnail_cancel_pending_requests();
939 
940    gfx_thumbnail_request(
941          ozone->thumbnail_path_data,
942          GFX_THUMBNAIL_RIGHT,
943          playlist,
944          selection,
945          &ozone->thumbnails.right,
946          gfx_thumbnail_upscale_threshold,
947          network_on_demand_thumbnails
948          );
949 
950    /* Image (and video/music) content requires special
951     * treatment... */
952    if (ozone->selection_core_is_viewer)
953    {
954       /* Left thumbnail is simply reset */
955       gfx_thumbnail_reset(&ozone->thumbnails.left);
956    }
957    else
958    {
959       /* Left thumbnail */
960       gfx_thumbnail_request(
961          ozone->thumbnail_path_data,
962          GFX_THUMBNAIL_LEFT,
963          playlist,
964          selection,
965          &ozone->thumbnails.left,
966          gfx_thumbnail_upscale_threshold,
967          network_on_demand_thumbnails);
968    }
969 }
970 
ozone_refresh_thumbnail_image(void * data,unsigned i)971 static void ozone_refresh_thumbnail_image(void *data, unsigned i)
972 {
973    ozone_handle_t *ozone            = (ozone_handle_t*)data;
974 
975    if (!ozone)
976       return;
977 
978    /* Only refresh thumbnails if thumbnails are enabled
979     * and we are currently viewing a playlist */
980    if ((gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
981         gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT)) &&
982        (ozone->is_playlist && ozone->depth == 1))
983       ozone_update_thumbnail_image(ozone);
984 }
985 
ozone_init_font(ozone_font_data_t * font_data,bool is_threaded,char * font_path,float font_size)986 static bool ozone_init_font(
987       ozone_font_data_t *font_data,
988       bool is_threaded, char *font_path, float font_size)
989 {
990    int glyph_width       = 0;
991    gfx_display_t *p_disp = disp_get_ptr();
992    const char *wideglyph_str = msg_hash_get_wideglyph_str();
993 
994    /* Free existing */
995    if (font_data->font)
996    {
997       gfx_display_font_free(font_data->font);
998       font_data->font = NULL;
999    }
1000 
1001    /* Cache approximate dimensions */
1002    font_data->line_height = (int)(font_size + 0.5f);
1003    font_data->glyph_width = (int)((font_size * (3.0f / 4.0f)) + 0.5f);
1004 
1005    /* Create font */
1006    font_data->font = gfx_display_font_file(p_disp,
1007          font_path, font_size, is_threaded);
1008 
1009    if (!font_data->font)
1010       return false;
1011 
1012    /* Get font metadata */
1013    glyph_width = font_driver_get_message_width(font_data->font, "a", 1, 1.0f);
1014    if (glyph_width > 0)
1015       font_data->glyph_width     = glyph_width;
1016 
1017    font_data->wideglyph_width = 100;
1018 
1019    if (wideglyph_str)
1020    {
1021       int wideglyph_width =
1022          font_driver_get_message_width(font_data->font, wideglyph_str, strlen(wideglyph_str), 1.0f);
1023 
1024       if (wideglyph_width > 0 && glyph_width > 0)
1025          font_data->wideglyph_width = wideglyph_width * 100 / glyph_width;
1026    }
1027 
1028    font_data->line_height        = font_driver_get_line_height(font_data->font, 1.0f);
1029    font_data->line_ascender      = font_driver_get_line_ascender(font_data->font, 1.0f);
1030    font_data->line_centre_offset = font_driver_get_line_centre_offset(font_data->font, 1.0f);
1031 
1032    return true;
1033 }
1034 
ozone_cache_footer_label(ozone_handle_t * ozone,ozone_footer_label_t * label,enum msg_hash_enums enum_idx)1035 static void ozone_cache_footer_label(ozone_handle_t *ozone,
1036       ozone_footer_label_t *label, enum msg_hash_enums enum_idx)
1037 {
1038    const char *str = msg_hash_to_str(enum_idx);
1039    /* Determine pixel width */
1040    unsigned length = (unsigned)strlen(str);
1041 
1042    /* Assign string */
1043    label->str      = str;
1044    label->width    = font_driver_get_message_width(
1045          ozone->fonts.footer.font,
1046          label->str, length, 1.0f);
1047    /* If font_driver_get_message_width() fails,
1048     * use predetermined glyph_width as a fallback */
1049    if (label->width < 0)
1050       label->width = length * ozone->fonts.footer.glyph_width;
1051 }
1052 
1053 /* Assigns footer label strings (based on current
1054  * menu language) and calculates pixel widths */
ozone_cache_footer_labels(ozone_handle_t * ozone)1055 static void ozone_cache_footer_labels(ozone_handle_t *ozone)
1056 {
1057    ozone_cache_footer_label(
1058          ozone, &ozone->footer_labels.ok,
1059          MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_OK);
1060 
1061    ozone_cache_footer_label(
1062          ozone, &ozone->footer_labels.back,
1063          MENU_ENUM_LABEL_VALUE_BASIC_MENU_CONTROLS_BACK);
1064 
1065    ozone_cache_footer_label(
1066          ozone, &ozone->footer_labels.search,
1067          MENU_ENUM_LABEL_VALUE_SEARCH);
1068 
1069    ozone_cache_footer_label(
1070          ozone, &ozone->footer_labels.fullscreen_thumbs,
1071          MSG_TOGGLE_FULLSCREEN_THUMBNAILS);
1072 
1073    ozone_cache_footer_label(
1074          ozone, &ozone->footer_labels.metadata_toggle,
1075          MSG_TOGGLE_CONTENT_METADATA);
1076 
1077    /* Record current language setting */
1078    ozone->footer_labels_language = *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE);
1079 }
1080 
1081 /* Determines the size of all menu elements */
ozone_set_layout(ozone_handle_t * ozone,settings_t * settings,bool is_threaded)1082 static void ozone_set_layout(
1083       ozone_handle_t *ozone,
1084       settings_t *settings,
1085       bool is_threaded)
1086 {
1087    char s1[PATH_MAX_LENGTH];
1088    char font_path[PATH_MAX_LENGTH];
1089    float scale_factor  = 0.0f;
1090    bool font_inited    = false;
1091 
1092    font_path[0] = s1[0]= '\0';
1093 
1094    if (!ozone)
1095       return;
1096 
1097    scale_factor = ozone->last_scale_factor;
1098 
1099    /* Calculate dimensions */
1100    ozone->dimensions.header_height                 = HEADER_HEIGHT * scale_factor;
1101    ozone->dimensions.footer_height                 = FOOTER_HEIGHT * scale_factor;
1102 
1103    ozone->dimensions.entry_padding_horizontal_half = ENTRY_PADDING_HORIZONTAL_HALF * scale_factor;
1104    ozone->dimensions.entry_padding_horizontal_full = ENTRY_PADDING_HORIZONTAL_FULL * scale_factor;
1105    ozone->dimensions.entry_padding_vertical        = ENTRY_PADDING_VERTICAL * scale_factor;
1106    ozone->dimensions.entry_height                  = ENTRY_HEIGHT * scale_factor;
1107    ozone->dimensions.entry_spacing                 = ENTRY_SPACING * scale_factor;
1108    ozone->dimensions.entry_icon_size               = ENTRY_ICON_SIZE * scale_factor;
1109    ozone->dimensions.entry_icon_padding            = ENTRY_ICON_PADDING * scale_factor;
1110 
1111    ozone->dimensions.sidebar_entry_height           = SIDEBAR_ENTRY_HEIGHT * scale_factor;
1112    ozone->dimensions.sidebar_padding_horizontal     = SIDEBAR_X_PADDING * scale_factor;
1113    ozone->dimensions.sidebar_padding_vertical       = SIDEBAR_Y_PADDING * scale_factor;
1114    ozone->dimensions.sidebar_entry_padding_vertical = SIDEBAR_ENTRY_Y_PADDING * scale_factor;
1115    ozone->dimensions.sidebar_entry_icon_size        = SIDEBAR_ENTRY_ICON_SIZE * scale_factor;
1116    ozone->dimensions.sidebar_entry_icon_padding     = SIDEBAR_ENTRY_ICON_PADDING * scale_factor;
1117    ozone->dimensions.sidebar_gradient_height        = SIDEBAR_GRADIENT_HEIGHT * scale_factor;
1118 
1119    ozone->dimensions.sidebar_width_normal             = SIDEBAR_WIDTH * scale_factor;
1120    ozone->dimensions.sidebar_width_collapsed          =
1121          ozone->dimensions.sidebar_entry_icon_size +
1122          ozone->dimensions.sidebar_entry_icon_padding * 2 +
1123          ozone->dimensions.sidebar_padding_horizontal * 2;
1124 
1125    if (ozone->dimensions_sidebar_width == 0)
1126       ozone->dimensions_sidebar_width = (float)ozone->dimensions.sidebar_width_normal;
1127 
1128    ozone->dimensions.thumbnail_bar_width          =
1129          ozone->dimensions.sidebar_width_normal -
1130          ozone->dimensions.sidebar_entry_icon_size -
1131          ozone->dimensions.sidebar_entry_icon_padding;
1132 
1133    ozone->dimensions.cursor_size                  = CURSOR_SIZE * scale_factor;
1134 
1135    ozone->dimensions.fullscreen_thumbnail_padding = FULLSCREEN_THUMBNAIL_PADDING * scale_factor;
1136 
1137    ozone->dimensions.spacer_1px = (scale_factor > 1.0f) ? (unsigned)(scale_factor + 0.5f) : 1;
1138    ozone->dimensions.spacer_2px = ozone->dimensions.spacer_1px * 2;
1139    ozone->dimensions.spacer_3px = (unsigned)((scale_factor * 3.0f) + 0.5f);
1140    ozone->dimensions.spacer_5px = (unsigned)((scale_factor * 5.0f) + 0.5f);
1141 
1142    /* Determine movement delta size for activating
1143     * pointer input (note: not a dimension as such,
1144     * so not included in the 'dimensions' struct) */
1145    ozone->pointer_active_delta = CURSOR_ACTIVE_DELTA * scale_factor;
1146 
1147    /* Initialise fonts */
1148    switch (*msg_hash_get_uint(MSG_HASH_USER_LANGUAGE))
1149    {
1150       case RETRO_LANGUAGE_ARABIC:
1151       case RETRO_LANGUAGE_PERSIAN:
1152          fill_pathname_application_special(s1, sizeof(s1),
1153                APPLICATION_SPECIAL_DIRECTORY_ASSETS_PKG);
1154          fill_pathname_join(font_path, s1, "fallback-font.ttf", sizeof(font_path));
1155          break;
1156       case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
1157       case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
1158          fill_pathname_application_special(s1, sizeof(s1),
1159                APPLICATION_SPECIAL_DIRECTORY_ASSETS_PKG);
1160          fill_pathname_join(font_path, s1, "chinese-fallback-font.ttf", sizeof(font_path));
1161          break;
1162       case RETRO_LANGUAGE_KOREAN:
1163          fill_pathname_application_special(s1, sizeof(s1),
1164                APPLICATION_SPECIAL_DIRECTORY_ASSETS_PKG);
1165          fill_pathname_join(font_path, s1, "korean-fallback-font.ttf", sizeof(font_path));
1166          break;
1167       default:
1168          fill_pathname_join(font_path, ozone->assets_path, "bold.ttf", sizeof(font_path));
1169    }
1170 
1171    font_inited = ozone_init_font(&ozone->fonts.title,
1172          is_threaded, font_path, FONT_SIZE_TITLE * scale_factor);
1173    ozone->has_all_assets = ozone->has_all_assets && font_inited;
1174 
1175    switch (*msg_hash_get_uint(MSG_HASH_USER_LANGUAGE))
1176    {
1177       case RETRO_LANGUAGE_ARABIC:
1178       case RETRO_LANGUAGE_PERSIAN:
1179          fill_pathname_application_special(s1, sizeof(s1),
1180                APPLICATION_SPECIAL_DIRECTORY_ASSETS_PKG);
1181          fill_pathname_join(font_path, s1, "fallback-font.ttf", sizeof(font_path));
1182          break;
1183       case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
1184       case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
1185          fill_pathname_application_special(s1, sizeof(s1),
1186                APPLICATION_SPECIAL_DIRECTORY_ASSETS_PKG);
1187          fill_pathname_join(font_path, s1, "chinese-fallback-font.ttf", sizeof(font_path));
1188          break;
1189       case RETRO_LANGUAGE_KOREAN:
1190          fill_pathname_application_special(s1, sizeof(s1),
1191                APPLICATION_SPECIAL_DIRECTORY_ASSETS_PKG);
1192          fill_pathname_join(font_path, s1, "korean-fallback-font.ttf", sizeof(font_path));
1193          break;
1194       default:
1195          fill_pathname_join(font_path, ozone->assets_path, "regular.ttf", sizeof(font_path));
1196    }
1197 
1198    font_inited = ozone_init_font(&ozone->fonts.footer,
1199          is_threaded, font_path, FONT_SIZE_FOOTER * scale_factor);
1200    ozone->has_all_assets = ozone->has_all_assets && font_inited;
1201 
1202    font_inited = ozone_init_font(&ozone->fonts.time,
1203          is_threaded, font_path, FONT_SIZE_TIME * scale_factor);
1204    ozone->has_all_assets = ozone->has_all_assets && font_inited;
1205 
1206    font_inited = ozone_init_font(&ozone->fonts.entries_label,
1207          is_threaded, font_path, FONT_SIZE_ENTRIES_LABEL * scale_factor);
1208    ozone->has_all_assets = ozone->has_all_assets && font_inited;
1209 
1210    font_inited = ozone_init_font(&ozone->fonts.entries_sublabel,
1211          is_threaded, font_path, FONT_SIZE_ENTRIES_SUBLABEL * scale_factor);
1212    ozone->has_all_assets = ozone->has_all_assets && font_inited;
1213 
1214    font_inited = ozone_init_font(&ozone->fonts.sidebar,
1215          is_threaded, font_path, FONT_SIZE_SIDEBAR * scale_factor);
1216    ozone->has_all_assets = ozone->has_all_assets && font_inited;
1217 
1218    /* Cache footer text labels
1219     * > Fonts have been (re)initialised, so need
1220     *   to recalculate label widths */
1221    ozone_cache_footer_labels(ozone);
1222 
1223    /* Multiple sidebar parameters are set via animations
1224     * > ozone_refresh_sidebars() cancels any existing
1225     *   animations and 'force updates' the affected
1226     *   variables with newly scaled values */
1227    ozone_refresh_sidebars(ozone, settings, ozone->last_height);
1228 
1229    /* Entry dimensions must be recalculated after
1230     * updating menu layout */
1231    ozone->need_compute = true;
1232 }
1233 
ozone_context_reset(void * data,bool is_threaded)1234 static void ozone_context_reset(void *data, bool is_threaded)
1235 {
1236    unsigned i;
1237    ozone_handle_t *ozone = (ozone_handle_t*) data;
1238    settings_t *settings  = config_get_ptr();
1239 
1240    if (ozone)
1241    {
1242       ozone->has_all_assets = true;
1243 
1244       ozone_set_layout(ozone, settings, is_threaded);
1245 
1246       /* Textures init */
1247       for (i = 0; i < OZONE_TEXTURE_LAST; i++)
1248       {
1249          char filename[PATH_MAX_LENGTH];
1250          filename[0] = '\0';
1251 #if 0
1252          if (i == OZONE_TEXTURE_DISCORD_OWN_AVATAR && discord_avatar_is_ready())
1253             strlcpy(filename, discord_get_own_avatar(), sizeof(filename));
1254          else
1255 #endif
1256             strlcpy(filename, OZONE_TEXTURES_FILES[i], sizeof(filename));
1257 
1258          strlcat(filename, FILE_PATH_PNG_EXTENSION, sizeof(filename));
1259 
1260 #if 0
1261          if (i == OZONE_TEXTURE_DISCORD_OWN_AVATAR && discord_avatar_is_ready())
1262          {
1263             char buf[PATH_MAX_LENGTH];
1264             fill_pathname_application_special(buf,
1265                sizeof(buf),
1266                APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS);
1267             if (!gfx_display_reset_textures_list(filename, buf, &ozone->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL))
1268                RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->png_path,
1269                      PATH_DEFAULT_SLASH(), filename);
1270          }
1271          else
1272          {
1273 #endif
1274             if (!gfx_display_reset_textures_list(filename, ozone->png_path, &ozone->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL))
1275                ozone->has_all_assets = false;
1276 #if 0
1277          }
1278 #endif
1279       }
1280 
1281       /* Sidebar textures */
1282       for (i = 0; i < OZONE_TAB_TEXTURE_LAST; i++)
1283       {
1284          char filename[PATH_MAX_LENGTH];
1285 
1286          filename[0]        = '\0';
1287          strlcpy(filename,
1288                OZONE_TAB_TEXTURES_FILES[i], sizeof(filename));
1289          strlcat(filename, FILE_PATH_PNG_EXTENSION, sizeof(filename));
1290 
1291          if (!gfx_display_reset_textures_list(filename, ozone->tab_path, &ozone->tab_textures[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL))
1292          {
1293             ozone->has_all_assets = false;
1294             RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->tab_path,
1295                   PATH_DEFAULT_SLASH(), filename);
1296          }
1297       }
1298 
1299       /* Theme textures */
1300       if (!ozone_reset_theme_textures(ozone))
1301          ozone->has_all_assets = false;
1302 
1303       /* Icons textures init */
1304       for (i = 0; i < OZONE_ENTRIES_ICONS_TEXTURE_LAST; i++)
1305       {
1306          if (!gfx_display_reset_textures_list(ozone_entries_icon_texture_path(i), ozone->icons_path, &ozone->icons_textures[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL))
1307          {
1308             ozone->has_all_assets = false;
1309             RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->icons_path,
1310                   PATH_DEFAULT_SLASH(), ozone_entries_icon_texture_path(i));
1311          }
1312       }
1313 
1314       if (gfx_display_white_texture)
1315          video_driver_texture_unload(&gfx_display_white_texture);
1316       gfx_display_init_white_texture(gfx_display_white_texture);
1317 
1318       /* Horizontal list */
1319       ozone_context_reset_horizontal_list(ozone);
1320 
1321       /* State reset */
1322       ozone->fade_direction               = false;
1323       ozone->cursor_in_sidebar            = false;
1324       ozone->cursor_in_sidebar_old        = false;
1325       ozone->draw_old_list                = false;
1326       ozone->messagebox_state             = false;
1327       ozone->messagebox_state_old         = false;
1328 
1329       ozone->cursor_wiggle_state.wiggling = false;
1330 
1331       /* Animations */
1332       ozone->animations.cursor_alpha   = 1.0f;
1333       ozone->animations.scroll_y       = 0.0f;
1334       ozone->animations.list_alpha     = 1.0f;
1335 
1336       /* Missing assets message */
1337       if (!ozone->has_all_assets)
1338          runloop_msg_queue_push(msg_hash_to_str(MSG_MISSING_ASSETS), 1, 256, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
1339 
1340       /* Thumbnails */
1341       ozone_update_thumbnail_image(ozone);
1342 
1343       /* TODO: update savestate thumbnail image */
1344 
1345       ozone_restart_cursor_animation(ozone);
1346 
1347       /* Screensaver */
1348       menu_screensaver_context_destroy(ozone->screensaver);
1349    }
1350    video_driver_monitor_reset();
1351 }
1352 
ozone_collapse_end(void * userdata)1353 static void ozone_collapse_end(void *userdata)
1354 {
1355    ozone_handle_t *ozone = (ozone_handle_t*) userdata;
1356    ozone->draw_sidebar   = false;
1357 }
1358 
ozone_unload_thumbnail_textures(void * data)1359 static void ozone_unload_thumbnail_textures(void *data)
1360 {
1361    ozone_handle_t *ozone = (ozone_handle_t*)data;
1362    if (!ozone)
1363       return;
1364 
1365    gfx_thumbnail_cancel_pending_requests();
1366    gfx_thumbnail_reset(&ozone->thumbnails.right);
1367    gfx_thumbnail_reset(&ozone->thumbnails.left);
1368 }
1369 
ozone_font_free(ozone_font_data_t * font_data)1370 static void INLINE ozone_font_free(ozone_font_data_t *font_data)
1371 {
1372    if (font_data->font)
1373       gfx_display_font_free(font_data->font);
1374 
1375    font_data->font = NULL;
1376 }
1377 
ozone_context_destroy(void * data)1378 static void ozone_context_destroy(void *data)
1379 {
1380    unsigned i;
1381    uintptr_t tag;
1382    ozone_handle_t *ozone = (ozone_handle_t*) data;
1383 
1384    if (!ozone)
1385       return;
1386 
1387    /* Theme */
1388    ozone_unload_theme_textures(ozone);
1389 
1390    /* Icons */
1391    for (i = 0; i < OZONE_ENTRIES_ICONS_TEXTURE_LAST; i++)
1392       video_driver_texture_unload(&ozone->icons_textures[i]);
1393 
1394    /* Textures */
1395    for (i = 0; i < OZONE_TEXTURE_LAST; i++)
1396       video_driver_texture_unload(&ozone->textures[i]);
1397 
1398    /* Icons */
1399    for (i = 0; i < OZONE_TAB_TEXTURE_LAST; i++)
1400       video_driver_texture_unload(&ozone->tab_textures[i]);
1401 
1402    /* Thumbnails */
1403    ozone_unload_thumbnail_textures(ozone);
1404 
1405    video_driver_texture_unload(&gfx_display_white_texture);
1406 
1407    /* Fonts */
1408    ozone_font_free(&ozone->fonts.footer);
1409    ozone_font_free(&ozone->fonts.title);
1410    ozone_font_free(&ozone->fonts.time);
1411    ozone_font_free(&ozone->fonts.entries_label);
1412    ozone_font_free(&ozone->fonts.entries_sublabel);
1413    ozone_font_free(&ozone->fonts.sidebar);
1414 
1415    tag = (uintptr_t) &ozone_default_theme;
1416    gfx_animation_kill_by_tag(&tag);
1417 
1418    /* Horizontal list */
1419    ozone_context_destroy_horizontal_list(ozone);
1420 
1421    /* Screensaver */
1422    menu_screensaver_context_destroy(ozone->screensaver);
1423 }
1424 
ozone_list_get_entry(void * data,enum menu_list_type type,unsigned i)1425 static void *ozone_list_get_entry(void *data,
1426       enum menu_list_type type, unsigned i)
1427 {
1428    size_t list_size        = 0;
1429    ozone_handle_t* ozone   = (ozone_handle_t*) data;
1430 
1431    switch (type)
1432    {
1433       case MENU_LIST_PLAIN:
1434          {
1435             file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0);
1436             list_size  = menu_entries_get_stack_size(0);
1437             if (i < list_size)
1438                return (void*)&menu_stack->list[i];
1439          }
1440          break;
1441       case MENU_LIST_HORIZONTAL:
1442          list_size = ozone->horizontal_list.size;
1443          if (i < list_size)
1444             return (void*)&ozone->horizontal_list.list[i];
1445          break;
1446       default:
1447          break;
1448    }
1449 
1450    return NULL;
1451 }
1452 
ozone_list_push(void * data,void * userdata,menu_displaylist_info_t * info,unsigned type)1453 static int ozone_list_push(void *data, void *userdata,
1454       menu_displaylist_info_t *info, unsigned type)
1455 {
1456    int ret                = -1;
1457    core_info_list_t *list = NULL;
1458 
1459    switch (type)
1460    {
1461       case DISPLAYLIST_LOAD_CONTENT_LIST:
1462          {
1463             settings_t             *settings = config_get_ptr();
1464             bool menu_content_show_playlists = settings->bools.menu_content_show_playlists;
1465             bool kiosk_mode_enable           = settings->bools.kiosk_mode_enable;
1466 
1467             menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list);
1468 
1469             menu_entries_append_enum(info->list,
1470                   msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FAVORITES),
1471                   msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES),
1472                   MENU_ENUM_LABEL_FAVORITES,
1473                   MENU_SETTING_ACTION_FAVORITES_DIR, 0, 0);
1474 
1475             core_info_get_list(&list);
1476             if (list->info_count > 0)
1477             {
1478                menu_entries_append_enum(info->list,
1479                      msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DOWNLOADED_FILE_DETECT_CORE_LIST),
1480                      msg_hash_to_str(MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST),
1481                      MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST,
1482                      MENU_SETTING_ACTION, 0, 0);
1483             }
1484 
1485             if (menu_content_show_playlists)
1486                menu_entries_append_enum(info->list,
1487                      msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLISTS_TAB),
1488                      msg_hash_to_str(MENU_ENUM_LABEL_PLAYLISTS_TAB),
1489                      MENU_ENUM_LABEL_PLAYLISTS_TAB,
1490                      MENU_SETTING_ACTION, 0, 0);
1491 
1492             if (frontend_driver_parse_drive_list(info->list, true) != 0)
1493                menu_entries_append_enum(info->list, "/",
1494                      msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
1495                      MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR,
1496                      MENU_SETTING_ACTION, 0, 0);
1497 
1498             if (!kiosk_mode_enable)
1499             {
1500                menu_entries_append_enum(info->list,
1501                      msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MENU_FILE_BROWSER_SETTINGS),
1502                      msg_hash_to_str(MENU_ENUM_LABEL_MENU_FILE_BROWSER_SETTINGS),
1503                      MENU_ENUM_LABEL_MENU_FILE_BROWSER_SETTINGS,
1504                      MENU_SETTING_ACTION, 0, 0);
1505             }
1506 
1507             info->need_push    = true;
1508             info->need_refresh = true;
1509             ret = 0;
1510          }
1511          break;
1512       case DISPLAYLIST_MAIN_MENU:
1513          {
1514             settings_t   *settings      = config_get_ptr();
1515             rarch_system_info_t *system = runloop_get_system_info();
1516             menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list);
1517 
1518             if (rarch_ctl(RARCH_CTL_CORE_IS_RUNNING, NULL))
1519             {
1520                if (!rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL))
1521                {
1522                   MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1523                         info->list,
1524                         MENU_ENUM_LABEL_CONTENT_SETTINGS,
1525                         PARSE_ACTION,
1526                         false);
1527                }
1528             }
1529             else
1530             {
1531                if (system->load_no_content)
1532                {
1533                   MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1534                         info->list,
1535                         MENU_ENUM_LABEL_START_CORE,
1536                         PARSE_ACTION,
1537                         false);
1538                }
1539 
1540 #ifndef HAVE_DYNAMIC
1541                if (frontend_driver_has_fork())
1542 #endif
1543                {
1544                   if (settings->bools.menu_show_load_core)
1545                   {
1546                      MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1547                            info->list,
1548                            MENU_ENUM_LABEL_CORE_LIST,
1549                            PARSE_ACTION,
1550                            false);
1551                   }
1552                }
1553             }
1554 
1555             if (settings->bools.menu_show_load_content)
1556             {
1557                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1558                      info->list,
1559                      MENU_ENUM_LABEL_LOAD_CONTENT_LIST,
1560                      PARSE_ACTION,
1561                      false);
1562 
1563                if (menu_displaylist_has_subsystems())
1564                {
1565                   MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1566                         info->list,
1567                         MENU_ENUM_LABEL_SUBSYSTEM_SETTINGS,
1568                         PARSE_ACTION,
1569                         false);
1570                }
1571             }
1572 
1573             if (settings->bools.menu_show_load_disc)
1574             {
1575                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1576                      info->list,
1577                      MENU_ENUM_LABEL_LOAD_DISC,
1578                      PARSE_ACTION,
1579                      false);
1580             }
1581 
1582             if (settings->bools.menu_show_dump_disc)
1583             {
1584                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1585                      info->list,
1586                      MENU_ENUM_LABEL_DUMP_DISC,
1587                      PARSE_ACTION,
1588                      false);
1589             }
1590 
1591             MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1592                   info->list,
1593                   MENU_ENUM_LABEL_ADD_CONTENT_LIST,
1594                   PARSE_ACTION,
1595                   false);
1596 #ifdef HAVE_QT
1597             if (settings->bools.desktop_menu_enable)
1598             {
1599                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1600                      info->list,
1601                      MENU_ENUM_LABEL_SHOW_WIMP,
1602                      PARSE_ACTION,
1603                      false);
1604             }
1605 #endif
1606 #if defined(HAVE_NETWORKING)
1607 #if defined(HAVE_ONLINE_UPDATER)
1608             if (settings->bools.menu_show_online_updater && !settings->bools.kiosk_mode_enable)
1609             {
1610                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1611                      info->list,
1612                      MENU_ENUM_LABEL_ONLINE_UPDATER,
1613                      PARSE_ACTION,
1614                      false);
1615             }
1616 #endif
1617 #endif
1618             if (!settings->bools.menu_content_show_settings && !string_is_empty(settings->paths.menu_content_show_settings_password))
1619             {
1620                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1621                      info->list,
1622                      MENU_ENUM_LABEL_XMB_MAIN_MENU_ENABLE_SETTINGS,
1623                      PARSE_ACTION,
1624                      false);
1625             }
1626 
1627             if (settings->bools.kiosk_mode_enable && !string_is_empty(settings->paths.kiosk_mode_password))
1628             {
1629                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1630                      info->list,
1631                      MENU_ENUM_LABEL_MENU_DISABLE_KIOSK_MODE,
1632                      PARSE_ACTION,
1633                      false);
1634             }
1635 
1636             if (settings->bools.menu_show_information)
1637             {
1638                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1639                      info->list,
1640                      MENU_ENUM_LABEL_INFORMATION_LIST,
1641                      PARSE_ACTION,
1642                      false);
1643             }
1644 
1645 #if defined(HAVE_LAKKA_SWITCH) || defined(HAVE_LIBNX)
1646             MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1647                   info->list,
1648                   MENU_ENUM_LABEL_SWITCH_CPU_PROFILE,
1649                   PARSE_ACTION,
1650                   false);
1651 #endif
1652 
1653 #ifdef HAVE_LAKKA_SWITCH
1654             MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1655                   info->list,
1656                   MENU_ENUM_LABEL_SWITCH_GPU_PROFILE,
1657                   PARSE_ACTION,
1658                   false);
1659 #endif
1660 
1661             if (settings->bools.menu_show_configurations && !settings->bools.kiosk_mode_enable)
1662             {
1663                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1664                      info->list,
1665                      MENU_ENUM_LABEL_CONFIGURATIONS_LIST,
1666                      PARSE_ACTION,
1667                      false);
1668             }
1669 
1670             if (settings->bools.menu_show_help)
1671             {
1672                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1673                      info->list,
1674                      MENU_ENUM_LABEL_HELP_LIST,
1675                      PARSE_ACTION,
1676                      false);
1677             }
1678 
1679 #if !defined(IOS)
1680             if (settings->bools.menu_show_restart_retroarch)
1681             {
1682                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1683                      info->list,
1684                      MENU_ENUM_LABEL_RESTART_RETROARCH,
1685                      PARSE_ACTION,
1686                      false);
1687             }
1688 
1689             if (settings->bools.menu_show_quit_retroarch)
1690             {
1691                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1692                      info->list,
1693                      MENU_ENUM_LABEL_QUIT_RETROARCH,
1694                      PARSE_ACTION,
1695                      false);
1696             }
1697 #endif
1698 
1699             if (settings->bools.menu_show_reboot)
1700             {
1701                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1702                      info->list,
1703                      MENU_ENUM_LABEL_REBOOT,
1704                      PARSE_ACTION,
1705                      false);
1706             }
1707 
1708             if (settings->bools.menu_show_shutdown)
1709             {
1710                MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(
1711                      info->list,
1712                      MENU_ENUM_LABEL_SHUTDOWN,
1713                      PARSE_ACTION,
1714                      false);
1715             }
1716 
1717             info->need_push    = true;
1718             ret = 0;
1719          }
1720          break;
1721    }
1722    return ret;
1723 }
1724 
ozone_list_get_selection(void * data)1725 static size_t ozone_list_get_selection(void *data)
1726 {
1727    ozone_handle_t *ozone      = (ozone_handle_t*)data;
1728 
1729    if (!ozone)
1730       return 0;
1731 
1732    return ozone->categories_selection_ptr;
1733 }
1734 
ozone_list_clear(file_list_t * list)1735 static void ozone_list_clear(file_list_t *list)
1736 {
1737    uintptr_t tag = (uintptr_t)list;
1738    gfx_animation_kill_by_tag(&tag);
1739 
1740    ozone_free_list_nodes(list, false);
1741 }
1742 
ozone_list_free(file_list_t * list,size_t a,size_t b)1743 static void ozone_list_free(file_list_t *list, size_t a, size_t b)
1744 {
1745    ozone_list_clear(list);
1746 }
1747 
ozone_render(void * data,unsigned width,unsigned height,bool is_idle)1748 static void ozone_render(void *data,
1749       unsigned width, unsigned height,
1750       bool is_idle)
1751 {
1752    size_t i;
1753    /* c.f. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323
1754     * On some platforms (e.g. 32-bit x86 without SSE),
1755     * gcc can produce inconsistent floating point results
1756     * depending upon optimisation level. This can break
1757     * floating point variable comparisons. A workaround is
1758     * to declare the affected variable as 'volatile', which
1759     * disables optimisations and removes excess precision
1760     * (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323#c87) */
1761    volatile float scale_factor;
1762    unsigned entries_end             = (unsigned)menu_entries_get_size();
1763    bool pointer_enabled             = false;
1764    unsigned language                = *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE);
1765    ozone_handle_t *ozone            = (ozone_handle_t*)data;
1766    gfx_display_t *p_disp            = disp_get_ptr();
1767    gfx_animation_t          *p_anim = anim_get_ptr();
1768    settings_t             *settings = config_get_ptr();
1769    if (!ozone)
1770       return;
1771 
1772    /* Check whether screen dimensions or menu scale
1773     * factor have changed */
1774    scale_factor = gfx_display_get_dpi_scale(p_disp, settings, width, height);
1775 
1776    if ((scale_factor != ozone->last_scale_factor) ||
1777        (width != ozone->last_width) ||
1778        (height != ozone->last_height))
1779    {
1780       ozone->last_scale_factor = scale_factor;
1781       ozone->last_width        = width;
1782       ozone->last_height       = height;
1783 
1784       /* Note: We don't need a full context reset here
1785        * > Just rescale layout, and reset frame time counter */
1786       ozone_set_layout(ozone, settings, video_driver_is_threaded());
1787       video_driver_monitor_reset();
1788    }
1789 
1790    if (ozone->need_compute)
1791    {
1792       ozone_compute_entries_position(ozone, settings, entries_end);
1793       ozone->need_compute = false;
1794    }
1795 
1796    /* Check whether menu language has changed
1797     * > If so, need to re-cache footer text labels */
1798    if (ozone->footer_labels_language != language)
1799    {
1800       ozone->footer_labels_language = language;
1801       ozone_cache_footer_labels(ozone);
1802    }
1803 
1804    ozone->selection = menu_navigation_get_selection();
1805 
1806    /* Need to update this each frame, otherwise touchscreen
1807     * input breaks when changing orientation */
1808    p_disp->framebuf_width  = width;
1809    p_disp->framebuf_height = height;
1810 
1811    /* Read pointer state */
1812    menu_input_get_pointer_state(&ozone->pointer);
1813 
1814    /* If menu screensaver is active, update
1815     * screensaver and return */
1816    if (ozone->show_screensaver)
1817    {
1818       menu_screensaver_iterate(
1819             ozone->screensaver,
1820             p_disp, p_anim,
1821             (enum menu_screensaver_effect)settings->uints.menu_screensaver_animation,
1822             settings->floats.menu_screensaver_animation_speed,
1823             ozone->theme->screensaver_tint,
1824             width, height,
1825             settings->paths.directory_assets);
1826       GFX_ANIMATION_CLEAR_ACTIVE(p_anim);
1827       return;
1828    }
1829 
1830    /* Check whether pointer is enabled */
1831    if (ozone->pointer.type != MENU_POINTER_DISABLED)
1832    {
1833       /* When using a mouse, entry under pointer is
1834        * automatically selected
1835        * > Must therefore filter out small movements,
1836        *   otherwise scrolling with the mouse wheel
1837        *   becomes impossible... */
1838       if (ozone->pointer.type == MENU_POINTER_MOUSE)
1839       {
1840          int16_t cursor_x_delta = ozone->pointer.x - ozone->cursor_x_old;
1841          int16_t cursor_y_delta = ozone->pointer.y - ozone->cursor_y_old;
1842 
1843          if ((cursor_x_delta > ozone->pointer_active_delta) ||
1844              (cursor_x_delta < -ozone->pointer_active_delta) ||
1845              (cursor_y_delta > ozone->pointer_active_delta) ||
1846              (cursor_y_delta < -ozone->pointer_active_delta))
1847             ozone->cursor_mode = true;
1848       }
1849       /* On touchscreens, just check for any movement */
1850       else
1851       {
1852          if ((ozone->pointer.x != ozone->cursor_x_old) ||
1853              (ozone->pointer.y != ozone->cursor_y_old))
1854             ozone->cursor_mode = true;
1855       }
1856    }
1857 
1858    ozone->cursor_x_old = ozone->pointer.x;
1859    ozone->cursor_y_old = ozone->pointer.y;
1860 
1861    /* Pointer is disabled when:
1862     * - Showing fullscreen thumbnails
1863     * - On-screen keyboard is active
1864     * - A message box is being displayed */
1865    pointer_enabled = ozone->cursor_mode &&
1866          !ozone->show_fullscreen_thumbnails &&
1867          !menu_input_dialog_get_display_kb() &&
1868          !ozone->should_draw_messagebox;
1869 
1870    /* Process pointer input, if required */
1871    if (pointer_enabled)
1872    {
1873       file_list_t *selection_buf  = menu_entries_get_selection_buf_ptr(0);
1874       uintptr_t     animation_tag = (uintptr_t)selection_buf;
1875 
1876       int entry_padding           = (ozone->depth == 1) ?
1877             ozone->dimensions.entry_padding_horizontal_half :
1878                   ozone->dimensions.entry_padding_horizontal_full;
1879       float entry_x               = ozone->dimensions_sidebar_width +
1880             ozone->sidebar_offset + entry_padding;
1881       float entry_width           = width - ozone->dimensions_sidebar_width -
1882             ozone->sidebar_offset - entry_padding * 2 -
1883             ozone->animations.thumbnail_bar_position;
1884       bool first_entry_found      = false;
1885       bool last_entry_found       = false;
1886 
1887       unsigned horizontal_list_size = (unsigned)ozone->horizontal_list.size;
1888       float category_height         = ozone->dimensions.sidebar_entry_height +
1889             ozone->dimensions.sidebar_entry_padding_vertical;
1890       bool first_category_found     = false;
1891       bool last_category_found      = false;
1892 
1893       /* Check whether pointer is operating on entries
1894        * or sidebar
1895        * > Note 1: Since touchscreens effectively 'lose their
1896        *   place' when a touch is released, we can only perform
1897        *   this this check if the pointer is currently
1898        *   pressed - i.e. we must preserve the values set the
1899        *   last time the screen was touched.
1900        *   With mouse input we have a permanent cursor, so this
1901        *   is not an issue
1902        * > Note 2: Windows seems to report negative pointer
1903        *   coordinates when the cursor goes off the left hand
1904        *   side of the screen/window, so checking whether
1905        *   pointer.x is less than the effective sidebar width
1906        *   generates a false positive when ozone->depth > 1.
1907        *   We therefore must also check whether the sidebar
1908        *   is currently being drawn */
1909       ozone->last_pointer_in_sidebar = ozone->pointer_in_sidebar;
1910       if ((ozone->pointer.type == MENU_POINTER_MOUSE) ||
1911            ozone->pointer.pressed)
1912          ozone->pointer_in_sidebar   = ozone->draw_sidebar &&
1913                (ozone->pointer.x < ozone->dimensions_sidebar_width
1914                 + ozone->sidebar_offset);
1915 
1916       /* If pointer has switched from entries to sidebar
1917        * or vice versa, must reset pointer acceleration */
1918       if (ozone->pointer_in_sidebar != ozone->last_pointer_in_sidebar)
1919       {
1920          menu_input_set_pointer_y_accel(0.0f);
1921          ozone->pointer.y_accel = 0.0f;
1922       }
1923 
1924       /* If pointer is a mouse, then automatically follow
1925        * mouse focus from entries to sidebar (and vice versa) */
1926       if (ozone->pointer.type == MENU_POINTER_MOUSE)
1927       {
1928          if (ozone->pointer_in_sidebar &&
1929              !ozone->last_pointer_in_sidebar &&
1930              !ozone->cursor_in_sidebar)
1931             ozone_go_to_sidebar(ozone, settings, animation_tag);
1932          else if (!ozone->pointer_in_sidebar &&
1933                   ozone->last_pointer_in_sidebar &&
1934                   ozone->cursor_in_sidebar)
1935             ozone_leave_sidebar(ozone, settings, animation_tag);
1936       }
1937 
1938       /* Update scrolling - must be done first, otherwise
1939        * cannot determine entry/category positions
1940        * > Entries */
1941       if (!ozone->pointer_in_sidebar)
1942       {
1943          float entry_bottom_boundary = height - ozone->dimensions.header_height -
1944                ozone->dimensions.spacer_1px - ozone->dimensions.footer_height -
1945                ozone->dimensions.entry_padding_vertical * 2;
1946 
1947          ozone->animations.scroll_y += ozone->pointer.y_accel;
1948 
1949          if (ozone->animations.scroll_y + ozone->entries_height < entry_bottom_boundary)
1950             ozone->animations.scroll_y = entry_bottom_boundary - ozone->entries_height;
1951 
1952          if (ozone->animations.scroll_y > 0.0f)
1953             ozone->animations.scroll_y = 0.0f;
1954       }
1955       /* > Sidebar
1956        * Only process sidebar input here if the
1957        * cursor is currently *in* the sidebar */
1958       else if (ozone->cursor_in_sidebar)
1959       {
1960          float sidebar_bottom_boundary = height -
1961                (ozone->dimensions.header_height + ozone->dimensions.spacer_1px) -
1962                ozone->dimensions.footer_height -
1963                ozone->dimensions.sidebar_padding_vertical;
1964          float sidebar_height          = ozone_get_sidebar_height(ozone);
1965 
1966          ozone->animations.scroll_y_sidebar += ozone->pointer.y_accel;
1967 
1968          if (ozone->animations.scroll_y_sidebar + sidebar_height < sidebar_bottom_boundary)
1969             ozone->animations.scroll_y_sidebar = sidebar_bottom_boundary - sidebar_height;
1970 
1971          if (ozone->animations.scroll_y_sidebar > 0.0f)
1972             ozone->animations.scroll_y_sidebar = 0.0f;
1973       }
1974 
1975       /* Regardless of pointer location, have to process
1976        * all entries/categories in order to determine
1977        * the indices of the first and last entries/categories
1978        * displayed on screen
1979        * > Needed so we can determine proper cursor positions
1980        *   when mixing pointer + gamepad/keyboard input */
1981 
1982       /* >> Loop over all entries */
1983       ozone->first_onscreen_entry = 0;
1984       ozone->last_onscreen_entry  = (entries_end > 0) ? entries_end - 1 : 0;
1985 
1986       for (i = 0; i < entries_end; i++)
1987       {
1988          float entry_y;
1989          ozone_node_t *node = (ozone_node_t*)selection_buf->list[i].userdata;
1990 
1991          /* Sanity check */
1992          if (!node)
1993             break;
1994 
1995          /* Get current entry y position */
1996          entry_y = ozone->dimensions.header_height + ozone->dimensions.spacer_1px +
1997                ozone->dimensions.entry_padding_vertical + ozone->animations.scroll_y +
1998                node->position_y;
1999 
2000          /* Check whether this is the first on screen entry */
2001          if (!first_entry_found)
2002          {
2003             if ((entry_y + node->height) > ozone->dimensions.header_height)
2004             {
2005                ozone->first_onscreen_entry = i;
2006                first_entry_found = true;
2007             }
2008          }
2009          /* Check whether this is the last on screen entry */
2010          else if (!last_entry_found)
2011          {
2012             if (entry_y > (height - ozone->dimensions.footer_height))
2013             {
2014                /* Current entry is off screen - get index
2015                 * of previous entry */
2016                if (i > 0)
2017                {
2018                   ozone->last_onscreen_entry = i - 1;
2019                   last_entry_found = true;
2020                }
2021             }
2022          }
2023 
2024          /* Track pointer input, if required */
2025          if (!ozone->pointer_in_sidebar &&
2026              first_entry_found &&
2027              !last_entry_found)
2028          {
2029             /* Check whether pointer is within the bounds
2030              * of the current entry */
2031             if ((ozone->pointer.x > entry_x) &&
2032                 (ozone->pointer.x < entry_x + entry_width) &&
2033                 (ozone->pointer.y > entry_y) &&
2034                 (ozone->pointer.y < entry_y + node->height))
2035             {
2036                /* Pointer selection is always updated */
2037                menu_input_set_pointer_selection((unsigned)i);
2038 
2039                /* If pointer is a mouse, then automatically
2040                 * select entry under cursor */
2041                if (ozone->pointer.type == MENU_POINTER_MOUSE)
2042                {
2043                   /* Note the fudge factor - cannot auto select
2044                    * items while drag-scrolling the entry list,
2045                    * so have to wait until pointer acceleration
2046                    * drops below a 'sensible' level... */
2047                   if (!ozone->cursor_in_sidebar &&
2048                       (i != ozone->selection) &&
2049                       (ozone->pointer.y_accel < ozone->last_scale_factor) &&
2050                       (ozone->pointer.y_accel > -ozone->last_scale_factor))
2051                   {
2052                      menu_navigation_set_selection(i);
2053 
2054                      /* If this is a playlist, must update thumbnails */
2055                      if (ozone->is_playlist && (ozone->depth == 1))
2056                      {
2057                         ozone_set_thumbnail_content(ozone, "");
2058                         ozone_update_thumbnail_image(ozone);
2059                      }
2060                   }
2061                }
2062 
2063                /* If pointer is pressed and stationary, and
2064                 * if pointer has been held for at least
2065                 * MENU_INPUT_PRESS_TIME_SHORT ms, automatically
2066                 * select current entry */
2067                if (ozone->pointer.pressed &&
2068                    !ozone->pointer.dragged &&
2069                    (ozone->pointer.press_duration >= MENU_INPUT_PRESS_TIME_SHORT) &&
2070                    (i != ozone->selection))
2071                {
2072                   menu_navigation_set_selection(i);
2073 
2074                   /* If we are currently in the sidebar, leave it */
2075                   if (ozone->cursor_in_sidebar)
2076                      ozone_leave_sidebar(ozone, settings, animation_tag);
2077                   /* If this is a playlist, must update thumbnails */
2078                   else if (ozone->is_playlist && (ozone->depth == 1))
2079                   {
2080                      ozone_set_thumbnail_content(ozone, "");
2081                      ozone_update_thumbnail_image(ozone);
2082                   }
2083                }
2084             }
2085          }
2086 
2087          if (last_entry_found)
2088             break;
2089       }
2090 
2091       /* >> Loop over all categories */
2092       ozone->first_onscreen_category = 0;
2093       ozone->last_onscreen_category  = ozone->system_tab_end + horizontal_list_size;
2094 
2095       for (i = 0; i < ozone->system_tab_end + horizontal_list_size + 1; i++)
2096       {
2097          /* Get current category y position */
2098          float category_y = ozone->dimensions.header_height + ozone->dimensions.spacer_1px +
2099                ozone->dimensions.sidebar_padding_vertical + (category_height * i) +
2100                ((i > ozone->system_tab_end) ?
2101                      (ozone->dimensions.sidebar_entry_padding_vertical + ozone->dimensions.spacer_1px) : 0) +
2102                ozone->animations.scroll_y_sidebar;
2103 
2104          /* Check whether this is the first on screen category */
2105          if (!first_category_found)
2106          {
2107             if ((category_y + category_height) > ozone->dimensions.header_height)
2108             {
2109                ozone->first_onscreen_category = i;
2110                first_category_found = true;
2111             }
2112          }
2113          /* Check whether this is the last on screen category */
2114          else if (!last_category_found)
2115          {
2116             if (category_y > (height - ozone->dimensions.footer_height))
2117             {
2118                /* Current category is off screen - get index
2119                 * of previous category */
2120                if (i > 0)
2121                {
2122                   ozone->last_onscreen_category = i - 1;
2123                   last_category_found = true;
2124                }
2125             }
2126          }
2127 
2128          /* Track pointer input, if required */
2129          if (ozone->pointer_in_sidebar &&
2130              ozone->cursor_in_sidebar &&
2131              first_category_found &&
2132              !last_category_found)
2133          {
2134             /* If pointer is within the bounds of the
2135              * current category, cache category index
2136              * (for use in next 'pointer up' event) */
2137             if ((ozone->pointer.y > category_y) &&
2138                 (ozone->pointer.y < category_y + category_height))
2139                ozone->pointer_categories_selection = i;
2140          }
2141 
2142          if (last_category_found)
2143             break;
2144       }
2145    }
2146 
2147    menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &i);
2148 
2149    if (i >= entries_end)
2150    {
2151       i = 0;
2152       menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &i);
2153    }
2154 
2155    GFX_ANIMATION_CLEAR_ACTIVE(p_anim);
2156 }
2157 
ozone_draw_header(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 battery_level_enable,bool timedate_enable)2158 static void ozone_draw_header(
2159       ozone_handle_t *ozone,
2160       gfx_display_t *p_disp,
2161       gfx_animation_t *p_anim,
2162       settings_t *settings,
2163       void *userdata,
2164       unsigned video_width,
2165       unsigned video_height,
2166       bool battery_level_enable,
2167       bool timedate_enable)
2168 {
2169    char title[255];
2170    gfx_animation_ctx_ticker_t ticker;
2171    gfx_animation_ctx_ticker_smooth_t ticker_smooth;
2172    static const char* const ticker_spacer = OZONE_TICKER_SPACER;
2173    unsigned ticker_x_offset  = 0;
2174    unsigned timedate_offset  = 0;
2175    bool use_smooth_ticker    = settings->bools.menu_ticker_smooth;
2176    float scale_factor        = ozone->last_scale_factor;
2177    unsigned logo_icon_size   = 60 * scale_factor;
2178    unsigned status_icon_size = 92 * scale_factor;
2179    unsigned seperator_margin = 30 * scale_factor;
2180    enum gfx_animation_ticker_type
2181       menu_ticker_type       = (enum gfx_animation_ticker_type)settings->uints.menu_ticker_type;
2182    gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
2183    float *col                        = ozone->theme->entries_icon;
2184 
2185    /* Initial ticker configuration */
2186    if (use_smooth_ticker)
2187    {
2188       ticker_smooth.idx           = p_anim->ticker_pixel_idx;
2189       ticker_smooth.font_scale    = 1.0f;
2190       ticker_smooth.type_enum     = menu_ticker_type;
2191       ticker_smooth.spacer        = ticker_spacer;
2192       ticker_smooth.x_offset      = &ticker_x_offset;
2193       ticker_smooth.dst_str_width = NULL;
2194    }
2195    else
2196    {
2197       ticker.idx                  = p_anim->ticker_idx;
2198       ticker.type_enum            = menu_ticker_type;
2199       ticker.spacer               = ticker_spacer;
2200    }
2201 
2202    /* Separator */
2203    gfx_display_draw_quad(
2204          p_disp,
2205          userdata,
2206          video_width,
2207          video_height,
2208          seperator_margin,
2209          ozone->dimensions.header_height,
2210          video_width - seperator_margin * 2,
2211          ozone->dimensions.spacer_1px,
2212          video_width,
2213          video_height,
2214          ozone->theme->header_footer_separator);
2215 
2216    /* Title */
2217    if (use_smooth_ticker)
2218    {
2219       ticker_smooth.font        = ozone->fonts.title.font;
2220       ticker_smooth.selected    = true;
2221       ticker_smooth.field_width = (video_width - (128 + 47 + 180) * scale_factor);
2222       ticker_smooth.src_str     = ozone->show_fullscreen_thumbnails ? ozone->fullscreen_thumbnail_label : ozone->title;
2223       ticker_smooth.dst_str     = title;
2224       ticker_smooth.dst_str_len = sizeof(title);
2225 
2226       gfx_animation_ticker_smooth(&ticker_smooth);
2227    }
2228    else
2229    {
2230       ticker.s        = title;
2231       ticker.len      = (video_width - (128 + 47 + 180) * scale_factor) / ozone->fonts.title.glyph_width;
2232       ticker.str      = ozone->show_fullscreen_thumbnails ? ozone->fullscreen_thumbnail_label : ozone->title;
2233       ticker.selected = true;
2234 
2235       gfx_animation_ticker(&ticker);
2236    }
2237 
2238    gfx_display_draw_text(
2239          ozone->fonts.title.font,
2240          title,
2241          ticker_x_offset + 128 * scale_factor,
2242            ozone->dimensions.header_height / 2
2243          + ozone->fonts.title.line_centre_offset,
2244          video_width,
2245          video_height,
2246          ozone->theme->text_rgba,
2247          TEXT_ALIGN_LEFT,
2248          1.0f,
2249          false,
2250          1.0f,
2251          false);
2252 
2253    /* Icon */
2254    if (dispctx)
2255    {
2256       if (dispctx->blend_begin)
2257          dispctx->blend_begin(userdata);
2258       if (dispctx->draw)
2259       {
2260 #if 0
2261          if (discord_avatar_is_ready())
2262             ozone_draw_icon(
2263                   p_disp,
2264                   userdata,
2265                   video_width,
2266                   video_height,
2267                   logo_icon_size,
2268                   logo_icon_size,
2269                   ozone->textures[OZONE_TEXTURE_DISCORD_OWN_AVATAR],
2270                   47 * scale_factor,
2271                   14 * scale_factor, /* Where does this come from...? */
2272                   video_width,
2273                   video_height,
2274                   0, 1, col);
2275          else
2276 #endif
2277             ozone_draw_icon(
2278                   p_disp,
2279                   userdata,
2280                   video_width,
2281                   video_height,
2282                   logo_icon_size,
2283                   logo_icon_size,
2284                   ozone->textures[OZONE_TEXTURE_RETROARCH],
2285                   47 * scale_factor,
2286                   (ozone->dimensions.header_height - logo_icon_size) / 2,
2287                   video_width,
2288                   video_height,
2289                   0, 1, col);
2290       }
2291       if (dispctx->blend_end)
2292          dispctx->blend_end(userdata);
2293    }
2294 
2295    /* Battery */
2296    if (battery_level_enable)
2297    {
2298       gfx_display_ctx_powerstate_t powerstate;
2299       char msg[12];
2300 
2301       msg[0] = '\0';
2302 
2303       powerstate.s   = msg;
2304       powerstate.len = sizeof(msg);
2305 
2306       menu_display_powerstate(&powerstate);
2307 
2308       if (powerstate.battery_enabled)
2309       {
2310          timedate_offset = 95 * scale_factor;
2311 
2312          gfx_display_draw_text(
2313                ozone->fonts.time.font,
2314                msg,
2315                video_width - 85 * scale_factor,
2316                  ozone->dimensions.header_height / 2
2317                + ozone->fonts.time.line_centre_offset,
2318                video_width,
2319                video_height,
2320                ozone->theme->text_rgba,
2321                TEXT_ALIGN_RIGHT,
2322                1.0f,
2323                false,
2324                1.0f,
2325                false);
2326 
2327          if (dispctx)
2328          {
2329             if (dispctx->blend_begin)
2330                dispctx->blend_begin(userdata);
2331             if (dispctx->draw)
2332                ozone_draw_icon(
2333                      p_disp,
2334                      userdata,
2335                      video_width,
2336                      video_height,
2337                      status_icon_size,
2338                      status_icon_size,
2339                      ozone->icons_textures[powerstate.charging? OZONE_ENTRIES_ICONS_TEXTURE_BATTERY_CHARGING : (powerstate.percent > 80)? OZONE_ENTRIES_ICONS_TEXTURE_BATTERY_FULL : (powerstate.percent > 60)? OZONE_ENTRIES_ICONS_TEXTURE_BATTERY_80 : (powerstate.percent > 40)? OZONE_ENTRIES_ICONS_TEXTURE_BATTERY_60 : (powerstate.percent > 20)? OZONE_ENTRIES_ICONS_TEXTURE_BATTERY_40 : OZONE_ENTRIES_ICONS_TEXTURE_BATTERY_20],
2340                      video_width - (60 + 56) * scale_factor,
2341                      0,
2342                      video_width,
2343                      video_height,
2344                      0, 1, col);
2345             if (dispctx->blend_end)
2346                dispctx->blend_end(userdata);
2347          }
2348       }
2349    }
2350 
2351    /* Timedate */
2352    if (timedate_enable)
2353    {
2354       gfx_display_ctx_datetime_t datetime;
2355       char timedate[255];
2356 
2357       timedate[0]             = '\0';
2358 
2359       datetime.s              = timedate;
2360       datetime.time_mode      = settings->uints.menu_timedate_style;
2361       datetime.date_separator = settings->uints.menu_timedate_date_separator;
2362       datetime.len            = sizeof(timedate);
2363 
2364       menu_display_timedate(&datetime);
2365 
2366       gfx_display_draw_text(
2367             ozone->fonts.time.font,
2368             timedate,
2369             video_width - (85 * scale_factor) - timedate_offset,
2370               ozone->dimensions.header_height / 2
2371             + ozone->fonts.time.line_centre_offset,
2372             video_width,
2373             video_height,
2374             ozone->theme->text_rgba,
2375             TEXT_ALIGN_RIGHT,
2376             1.0f,
2377             false,
2378             1.0f,
2379             false);
2380 
2381       if (dispctx)
2382       {
2383          if (dispctx->blend_begin)
2384             dispctx->blend_begin(userdata);
2385          if (dispctx->draw)
2386             ozone_draw_icon(
2387                   p_disp,
2388                   userdata,
2389                   video_width,
2390                   video_height,
2391                   status_icon_size,
2392                   status_icon_size,
2393                   ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CLOCK],
2394                   video_width - (60 + 56) * scale_factor - timedate_offset,
2395                   0,
2396                   video_width,
2397                   video_height,
2398                   0, 1, col);
2399          if (dispctx->blend_end)
2400             dispctx->blend_end(userdata);
2401       }
2402    }
2403 }
2404 
ozone_draw_footer(ozone_handle_t * ozone,gfx_display_t * p_disp,gfx_animation_t * p_anim,void * userdata,unsigned video_width,unsigned video_height,bool input_menu_swap_ok_cancel_buttons,settings_t * settings)2405 static void ozone_draw_footer(
2406       ozone_handle_t *ozone,
2407       gfx_display_t *p_disp,
2408       gfx_animation_t *p_anim,
2409       void *userdata,
2410       unsigned video_width,
2411       unsigned video_height,
2412       bool input_menu_swap_ok_cancel_buttons,
2413       settings_t *settings)
2414 {
2415    bool menu_core_enable                  = settings->bools.menu_core_enable;
2416    float scale_factor                     = ozone->last_scale_factor;
2417    unsigned seperator_margin              = 30 * scale_factor;
2418    float footer_margin                    = 59 * scale_factor;
2419    float footer_text_y                    = (float)video_height -
2420          (ozone->dimensions.footer_height / 2.0f) +
2421          ozone->fonts.footer.line_centre_offset;
2422    float icon_size                        = 35 * scale_factor;
2423    float icon_padding                     = 12 * scale_factor;
2424    float icon_y                           = (float)video_height -
2425          (ozone->dimensions.footer_height / 2.0f) -
2426          (icon_size / 2.0f);
2427    /* Button enable states
2428     * > Note: Only show 'metadata_toggle' if
2429     *   'fullscreen_thumbs' is shown. This condition
2430     *   should be guaranteed anyway, but enforce it
2431     *   here to prevent 'gaps' in the button list in
2432     *   the event of unknown errors */
2433    bool fullscreen_thumbnails_available =
2434          ozone->fullscreen_thumbnails_available &&
2435          !ozone->cursor_in_sidebar &&
2436          ozone->show_thumbnail_bar &&
2437          ((ozone->thumbnails.right.status != GFX_THUMBNAIL_STATUS_MISSING) ||
2438           (ozone->thumbnails.left.status  != GFX_THUMBNAIL_STATUS_MISSING)) &&
2439          (gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
2440           gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT));
2441    bool metadata_override_available     =
2442          fullscreen_thumbnails_available &&
2443          ozone_metadata_override_available(ozone);
2444    /* Determine x origin positions of each
2445     * button
2446     * > From right to left, these are ordered:
2447     *   - ok
2448     *   - back
2449     *   - search
2450     *   - toggle fullscreen thumbs (playlists only)
2451     *   - toggle metadata (playlists only) */
2452    float ok_x                = (float)video_width - footer_margin -
2453          ozone->footer_labels.ok.width - icon_size - icon_padding;
2454    float back_x              = ok_x -
2455          ozone->footer_labels.back.width - icon_size - (2.0f * icon_padding);
2456    float search_x            = back_x -
2457          ozone->footer_labels.search.width - icon_size - (2.0f * icon_padding);
2458    float fullscreen_thumbs_x = search_x -
2459          ozone->footer_labels.fullscreen_thumbs.width - icon_size - (2.0f * icon_padding);
2460    float metadata_toggle_x   = fullscreen_thumbs_x -
2461          ozone->footer_labels.metadata_toggle.width - icon_size - (2.0f * icon_padding);
2462    gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
2463    float *col                        = ozone->theme_dynamic.entries_icon;
2464 
2465    /* Separator */
2466    gfx_display_draw_quad(
2467          p_disp,
2468          userdata,
2469          video_width,
2470          video_height,
2471          seperator_margin,
2472          video_height - ozone->dimensions.footer_height,
2473          video_width - seperator_margin * 2,
2474          ozone->dimensions.spacer_1px,
2475          video_width,
2476          video_height,
2477          ozone->theme->header_footer_separator);
2478 
2479    /* Buttons */
2480 
2481    /* Draw icons */
2482    if (dispctx)
2483    {
2484       if (dispctx->blend_begin)
2485          dispctx->blend_begin(userdata);
2486       gfx_display_set_alpha(ozone->theme_dynamic.entries_icon, 1.0f);
2487 
2488       if (dispctx->draw)
2489       {
2490          /* > ok */
2491          ozone_draw_icon(
2492                p_disp,
2493                userdata,
2494                video_width,
2495                video_height,
2496                icon_size,
2497                icon_size,
2498                input_menu_swap_ok_cancel_buttons ?
2499                ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_D] :
2500                ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_R],
2501                ok_x,
2502                icon_y,
2503                video_width,
2504                video_height,
2505                0, 1, col);
2506 
2507          /* > back */
2508          ozone_draw_icon(
2509                p_disp,
2510                userdata,
2511                video_width,
2512                video_height,
2513                icon_size,
2514                icon_size,
2515                input_menu_swap_ok_cancel_buttons ?
2516                ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_R] :
2517                ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_D],
2518                back_x,
2519                icon_y,
2520                video_width,video_height,
2521                0, 1, col);
2522 
2523          /* > search */
2524          ozone_draw_icon(
2525                p_disp,
2526                userdata,
2527                video_width,
2528                video_height,
2529                icon_size,
2530                icon_size,
2531                ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_U],
2532                search_x,
2533                icon_y,
2534                video_width,video_height,
2535                0, 1, col);
2536 
2537          /* > fullscreen_thumbs */
2538          if (fullscreen_thumbnails_available)
2539             ozone_draw_icon(
2540                   p_disp,
2541                   userdata,
2542                   video_width,
2543                   video_height,
2544                   icon_size,
2545                   icon_size,
2546                   ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_START],
2547                   fullscreen_thumbs_x,
2548                   icon_y,
2549                   video_width,video_height,
2550                   0, 1, col);
2551 
2552          /* > metadata_toggle */
2553          if (metadata_override_available)
2554             ozone_draw_icon(
2555                   p_disp,
2556                   userdata,
2557                   video_width,
2558                   video_height,
2559                   icon_size,
2560                   icon_size,
2561                   ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_SELECT],
2562                   metadata_toggle_x,
2563                   icon_y,
2564                   video_width,video_height,
2565                   0, 1, col);
2566       }
2567 
2568       if (dispctx->blend_end)
2569          dispctx->blend_end(userdata);
2570    }
2571 
2572    /* Draw labels */
2573 
2574    /* > ok */
2575    gfx_display_draw_text(
2576          ozone->fonts.footer.font,
2577          ozone->footer_labels.ok.str,
2578          ok_x + icon_size + icon_padding,
2579          footer_text_y,
2580          video_width,
2581          video_height,
2582          ozone->theme->text_rgba,
2583          TEXT_ALIGN_LEFT,
2584          1.0f,
2585          false,
2586          1.0f,
2587          false);
2588 
2589    /* > back */
2590    gfx_display_draw_text(
2591          ozone->fonts.footer.font,
2592          ozone->footer_labels.back.str,
2593          back_x + icon_size + icon_padding,
2594          footer_text_y,
2595          video_width,
2596          video_height,
2597          ozone->theme->text_rgba,
2598          TEXT_ALIGN_LEFT,
2599          1.0f,
2600          false,
2601          1.0f,
2602          false);
2603 
2604    /* > search */
2605    gfx_display_draw_text(
2606          ozone->fonts.footer.font,
2607          ozone->footer_labels.search.str,
2608          search_x + icon_size + icon_padding,
2609          footer_text_y,
2610          video_width,
2611          video_height,
2612          ozone->theme->text_rgba,
2613          TEXT_ALIGN_LEFT,
2614          1.0f,
2615          false,
2616          1.0f,
2617          false);
2618 
2619    /* > fullscreen_thumbs */
2620    if (fullscreen_thumbnails_available)
2621       gfx_display_draw_text(
2622             ozone->fonts.footer.font,
2623             ozone->footer_labels.fullscreen_thumbs.str,
2624             fullscreen_thumbs_x + icon_size + icon_padding,
2625             footer_text_y,
2626             video_width,
2627             video_height,
2628             ozone->theme->text_rgba,
2629             TEXT_ALIGN_LEFT,
2630             1.0f,
2631             false,
2632             1.0f,
2633             false);
2634 
2635    /* > metadata_toggle */
2636    if (metadata_override_available)
2637       gfx_display_draw_text(
2638             ozone->fonts.footer.font,
2639             ozone->footer_labels.metadata_toggle.str,
2640             metadata_toggle_x + icon_size + icon_padding,
2641             footer_text_y,
2642             video_width,
2643             video_height,
2644             ozone->theme->text_rgba,
2645             TEXT_ALIGN_LEFT,
2646             1.0f,
2647             false,
2648             1.0f,
2649             false);
2650 
2651    /* Core title or Switch icon */
2652    if (menu_core_enable)
2653    {
2654       gfx_animation_ctx_ticker_t ticker;
2655       gfx_animation_ctx_ticker_smooth_t ticker_smooth;
2656       char core_title[255];
2657       char core_title_buf[255];
2658       int usable_width;
2659       bool use_smooth_ticker                          =
2660             settings->bools.menu_ticker_smooth;
2661       enum gfx_animation_ticker_type menu_ticker_type =
2662             (enum gfx_animation_ticker_type)settings->uints.menu_ticker_type;
2663             static const char* const ticker_spacer    = OZONE_TICKER_SPACER;
2664       unsigned ticker_x_offset                        = 0;
2665 
2666       core_title[0]     = '\0';
2667       core_title_buf[0] = '\0';
2668 
2669       /* Determine available width for core
2670        * title string */
2671       usable_width = metadata_override_available ?
2672             metadata_toggle_x :
2673                   fullscreen_thumbnails_available ?
2674                         fullscreen_thumbs_x :
2675                         search_x;
2676       usable_width -= footer_margin + (icon_padding * 3);
2677 
2678       if (usable_width > 0)
2679       {
2680          /* Get core title */
2681          menu_entries_get_core_title(core_title, sizeof(core_title));
2682 
2683          /* Configure and run ticker */
2684          if (use_smooth_ticker)
2685          {
2686             ticker_smooth.idx           = p_anim->ticker_pixel_idx;
2687             ticker_smooth.font_scale    = 1.0f;
2688             ticker_smooth.type_enum     = menu_ticker_type;
2689             ticker_smooth.spacer        = ticker_spacer;
2690             ticker_smooth.x_offset      = &ticker_x_offset;
2691             ticker_smooth.dst_str_width = NULL;
2692 
2693             ticker_smooth.font          = ozone->fonts.footer.font;
2694             ticker_smooth.selected      = true;
2695             ticker_smooth.field_width   = usable_width;
2696             ticker_smooth.src_str       = core_title;
2697             ticker_smooth.dst_str       = core_title_buf;
2698             ticker_smooth.dst_str_len   = sizeof(core_title_buf);
2699 
2700             gfx_animation_ticker_smooth(&ticker_smooth);
2701          }
2702          else
2703          {
2704             ticker.idx                  = p_anim->ticker_idx;
2705             ticker.type_enum            = menu_ticker_type;
2706             ticker.spacer               = ticker_spacer;
2707 
2708             ticker.s                    = core_title_buf;
2709             ticker.len                  = usable_width / ozone->fonts.footer.glyph_width;
2710             ticker.str                  = core_title;
2711             ticker.selected             = true;
2712 
2713             gfx_animation_ticker(&ticker);
2714          }
2715 
2716          /* Draw text */
2717          gfx_display_draw_text(
2718                ozone->fonts.footer.font,
2719                core_title_buf,
2720                ticker_x_offset + footer_margin,
2721                footer_text_y,
2722                video_width,
2723                video_height,
2724                ozone->theme->text_rgba,
2725                TEXT_ALIGN_LEFT,
2726                1.0f,
2727                false,
2728                1.0f,
2729                false);
2730       }
2731    }
2732 #ifdef HAVE_LIBNX
2733    else
2734    {
2735       if (dispctx)
2736       {
2737          if (dispctx->blend_begin)
2738             dispctx->blend_begin(userdata);
2739          if (dispctx->draw)
2740             ozone_draw_icon(
2741                   p_disp,
2742                   userdata,
2743                   video_width,
2744                   video_height,
2745                   69 * scale_factor,
2746                   30 * scale_factor,
2747                   ozone->theme->textures[OZONE_THEME_TEXTURE_SWITCH],
2748                   footer_margin,
2749                   video_height - ozone->dimensions.footer_height / 2 - 15 * scale_factor,
2750                   video_width,
2751                   video_height,
2752                   0,
2753                   1,
2754                   ozone->pure_white);
2755          if (dispctx->blend_end)
2756             dispctx->blend_end(userdata);
2757       }
2758    }
2759 #endif
2760 }
2761 
ozone_set_thumbnail_system(void * data,char * s,size_t len)2762 static void ozone_set_thumbnail_system(void *data, char*s, size_t len)
2763 {
2764    ozone_handle_t *ozone = (ozone_handle_t*)data;
2765    if (!ozone)
2766       return;
2767 
2768    gfx_thumbnail_set_system(
2769          ozone->thumbnail_path_data, s, playlist_get_cached());
2770 }
2771 
ozone_get_thumbnail_system(void * data,char * s,size_t len)2772 static void ozone_get_thumbnail_system(void *data, char*s, size_t len)
2773 {
2774    ozone_handle_t *ozone = (ozone_handle_t*)data;
2775    const char *system    = NULL;
2776    if (!ozone)
2777       return;
2778 
2779    if (gfx_thumbnail_get_system(ozone->thumbnail_path_data, &system))
2780       strlcpy(s, system, len);
2781 }
2782 
ozone_selection_changed(ozone_handle_t * ozone,bool allow_animation)2783 static void ozone_selection_changed(ozone_handle_t *ozone, bool allow_animation)
2784 {
2785    file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0);
2786    size_t new_selection       = menu_navigation_get_selection();
2787    ozone_node_t *node         = (ozone_node_t*)selection_buf->list[new_selection].userdata;
2788 
2789    if (!node)
2790       return;
2791 
2792    if (ozone->selection != new_selection)
2793    {
2794       menu_entry_t entry;
2795       unsigned entry_type;
2796       uintptr_t tag                = (uintptr_t)selection_buf;
2797       size_t selection             = menu_navigation_get_selection();
2798 
2799       MENU_ENTRY_INIT(entry);
2800       entry.path_enabled           = false;
2801       entry.label_enabled          = false;
2802       entry.rich_label_enabled     = false;
2803       entry.value_enabled          = false;
2804       entry.sublabel_enabled       = false;
2805       menu_entry_get(&entry, 0, selection, NULL, true);
2806 
2807       entry_type                   = entry.type;
2808 
2809       ozone->selection_old         = ozone->selection;
2810       ozone->selection             = new_selection;
2811 
2812       ozone->cursor_in_sidebar_old = ozone->cursor_in_sidebar;
2813 
2814       gfx_animation_kill_by_tag(&tag);
2815       ozone_update_scroll(ozone, allow_animation, node);
2816 
2817       /* Update thumbnail */
2818       if (gfx_thumbnail_is_enabled(
2819                ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
2820           gfx_thumbnail_is_enabled(
2821              ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
2822       {
2823          bool update_thumbnails = false;
2824 
2825          /* Playlist updates */
2826          if (ozone->is_playlist && ozone->depth == 1)
2827          {
2828             ozone_set_thumbnail_content(ozone, "");
2829             update_thumbnails = true;
2830          }
2831          /* Database list updates
2832           * (pointless nuisance...) */
2833          else if (ozone->depth == 4 && ozone->is_db_manager_list)
2834          {
2835             ozone_set_thumbnail_content(ozone, "");
2836             update_thumbnails = true;
2837          }
2838          /* Filebrowser image updates */
2839          else if (ozone->is_file_list)
2840          {
2841             if ((entry_type == FILE_TYPE_IMAGEVIEWER) ||
2842                 (entry_type == FILE_TYPE_IMAGE))
2843             {
2844                ozone_set_thumbnail_content(ozone, "imageviewer");
2845                update_thumbnails = true;
2846             }
2847             else
2848             {
2849                /* If this is a file list and current
2850                 * entry is not an image, have to 'reset'
2851                 * content + right/left thumbnails
2852                 * (otherwise last loaded thumbnail will
2853                 * persist, and be shown on the wrong entry) */
2854                gfx_thumbnail_set_content(ozone->thumbnail_path_data, NULL);
2855                ozone_unload_thumbnail_textures(ozone);
2856             }
2857          }
2858 
2859          if (update_thumbnails)
2860             ozone_update_thumbnail_image(ozone);
2861       }
2862 
2863       /* TODO: update savestate thumbnail and path */
2864    }
2865 }
2866 
ozone_navigation_clear(void * data,bool pending_push)2867 static void ozone_navigation_clear(void *data, bool pending_push)
2868 {
2869    ozone_handle_t *ozone = (ozone_handle_t*)data;
2870    if (!pending_push)
2871       ozone_selection_changed(ozone, true);
2872 }
2873 
ozone_navigation_pointer_changed(void * data)2874 static void ozone_navigation_pointer_changed(void *data)
2875 {
2876    ozone_handle_t *ozone = (ozone_handle_t*)data;
2877    ozone_selection_changed(ozone, true);
2878 }
2879 
ozone_navigation_set(void * data,bool scroll)2880 static void ozone_navigation_set(void *data, bool scroll)
2881 {
2882    ozone_handle_t *ozone = (ozone_handle_t*)data;
2883    ozone_selection_changed(ozone, true);
2884 }
2885 
ozone_navigation_alphabet(void * data,size_t * unused)2886 static void ozone_navigation_alphabet(void *data, size_t *unused)
2887 {
2888    ozone_handle_t *ozone = (ozone_handle_t*)data;
2889    ozone_selection_changed(ozone, true);
2890 }
2891 
ozone_messagebox_fadeout_cb(void * userdata)2892 static void ozone_messagebox_fadeout_cb(void *userdata)
2893 {
2894    ozone_handle_t *ozone = (ozone_handle_t*) userdata;
2895 
2896    free(ozone->pending_message);
2897    ozone->pending_message = NULL;
2898 
2899    ozone->should_draw_messagebox = false;
2900 }
2901 
ozone_font_bind(ozone_font_data_t * font_data)2902 static void INLINE ozone_font_bind(ozone_font_data_t *font_data)
2903 {
2904    font_driver_bind_block(font_data->font, &font_data->raster_block);
2905    font_data->raster_block.carr.coords.vertices = 0;
2906 }
2907 
ozone_font_unbind(ozone_font_data_t * font_data)2908 static void INLINE ozone_font_unbind(ozone_font_data_t *font_data)
2909 {
2910    font_driver_bind_block(font_data->font, NULL);
2911 }
2912 
ozone_frame(void * data,video_frame_info_t * video_info)2913 static void ozone_frame(void *data, video_frame_info_t *video_info)
2914 {
2915    gfx_animation_ctx_entry_t entry;
2916    ozone_handle_t* ozone                  = (ozone_handle_t*) data;
2917    settings_t  *settings                  = config_get_ptr();
2918    unsigned color_theme                   = settings->uints.menu_ozone_color_theme;
2919    bool use_preferred_system_color_theme  = settings->bools.menu_use_preferred_system_color_theme;
2920    uintptr_t messagebox_tag               = (uintptr_t)ozone->pending_message;
2921    bool draw_osk                          = menu_input_dialog_get_display_kb();
2922    static bool draw_osk_old               = false;
2923    float *background_color                = NULL;
2924    void *userdata                         = video_info->userdata;
2925    unsigned video_width                   = video_info->width;
2926    unsigned video_height                  = video_info->height;
2927    float menu_framebuffer_opacity         = video_info->menu_framebuffer_opacity;
2928    bool libretro_running                  = video_info->libretro_running;
2929    bool video_fullscreen                  = video_info->fullscreen;
2930    bool mouse_grabbed                     = video_info->input_driver_grab_mouse_state;
2931    bool menu_mouse_enable                 = video_info->menu_mouse_enable;
2932    bool input_menu_swap_ok_cancel_buttons = video_info->input_menu_swap_ok_cancel_buttons;
2933    bool battery_level_enable              = video_info->battery_level_enable;
2934    bool timedate_enable                   = video_info->timedate_enable;
2935    gfx_display_t            *p_disp       = (gfx_display_t*)video_info->disp_userdata;
2936    gfx_animation_t *p_anim                = anim_get_ptr();
2937    gfx_display_ctx_driver_t *dispctx      = p_disp->dispctx;
2938 
2939 #if 0
2940    static bool reset                      = false;
2941 
2942    if (discord_avatar_is_ready() && !reset)
2943    {
2944       ozone_context_reset(data, false);
2945       reset = true;
2946    }
2947 #endif
2948 
2949 
2950    if (!ozone)
2951       return;
2952 
2953    if (ozone->first_frame)
2954    {
2955       menu_input_get_pointer_state(&ozone->pointer);
2956 
2957       ozone->cursor_x_old = ozone->pointer.x;
2958       ozone->cursor_y_old = ozone->pointer.y;
2959       ozone->first_frame  = false;
2960    }
2961 
2962    /* OSK Fade detection */
2963    if (draw_osk != draw_osk_old)
2964    {
2965       draw_osk_old = draw_osk;
2966       if (!draw_osk)
2967       {
2968          ozone->should_draw_messagebox       = false;
2969          ozone->messagebox_state             = false;
2970          ozone->messagebox_state_old         = false;
2971          ozone->animations.messagebox_alpha  = 0.0f;
2972       }
2973    }
2974 
2975    /* Change theme on the fly */
2976    if ((color_theme != last_color_theme) ||
2977        (last_use_preferred_system_color_theme != use_preferred_system_color_theme))
2978    {
2979       if (use_preferred_system_color_theme)
2980       {
2981          color_theme                            = ozone_get_system_theme();
2982          configuration_set_uint(settings,
2983                settings->uints.menu_ozone_color_theme, color_theme);
2984       }
2985 
2986       ozone_set_color_theme(ozone, color_theme);
2987       ozone_set_background_running_opacity(ozone, menu_framebuffer_opacity);
2988 
2989       last_use_preferred_system_color_theme = use_preferred_system_color_theme;
2990    }
2991 
2992    /* If menu screensaver is active, draw
2993     * screensaver and return */
2994    if (ozone->show_screensaver)
2995    {
2996       menu_screensaver_frame(ozone->screensaver,
2997             video_info, p_disp);
2998       return;
2999    }
3000 
3001    video_driver_set_viewport(video_width, video_height, true, false);
3002 
3003    /* Clear text */
3004    ozone_font_bind(&ozone->fonts.footer);
3005    ozone_font_bind(&ozone->fonts.title);
3006    ozone_font_bind(&ozone->fonts.time);
3007    ozone_font_bind(&ozone->fonts.entries_label);
3008    ozone_font_bind(&ozone->fonts.entries_sublabel);
3009    ozone_font_bind(&ozone->fonts.sidebar);
3010 
3011    /* Background */
3012    if (libretro_running &&
3013        (menu_framebuffer_opacity < 1.0f))
3014    {
3015       if (menu_framebuffer_opacity != last_framebuffer_opacity)
3016          ozone_set_background_running_opacity(ozone, menu_framebuffer_opacity);
3017 
3018       background_color = ozone->theme->background_libretro_running;
3019    }
3020    else
3021       background_color = ozone->theme->background;
3022 
3023    gfx_display_draw_quad(
3024          p_disp,
3025          userdata,
3026          video_width,
3027          video_height,
3028          0, 0, video_width, video_height,
3029          video_width, video_height,
3030          background_color
3031          );
3032 
3033    /* Header, footer */
3034    ozone_draw_header(
3035          ozone,
3036          p_disp,
3037          p_anim,
3038          settings,
3039          userdata,
3040          video_width,
3041          video_height,
3042          battery_level_enable,
3043          timedate_enable);
3044    ozone_draw_footer(ozone,
3045          p_disp, p_anim,
3046          userdata,
3047          video_width,
3048          video_height,
3049          input_menu_swap_ok_cancel_buttons,
3050          settings);
3051 
3052    /* Sidebar */
3053    if (ozone->draw_sidebar)
3054       ozone_draw_sidebar(
3055             ozone,
3056             p_disp,
3057             p_anim,
3058             settings,
3059             userdata,
3060             video_width,
3061             video_height,
3062             libretro_running,
3063             menu_framebuffer_opacity);
3064 
3065    /* Menu entries */
3066    gfx_display_scissor_begin(p_disp,
3067          userdata,
3068          video_width,
3069          video_height,
3070          ozone->sidebar_offset + (unsigned) ozone->dimensions_sidebar_width,
3071          ozone->dimensions.header_height + ozone->dimensions.spacer_1px,
3072          video_width - (unsigned) ozone->dimensions_sidebar_width
3073          + (-ozone->sidebar_offset),
3074          video_height - ozone->dimensions.header_height - ozone->dimensions.footer_height - ozone->dimensions.spacer_1px);
3075 
3076    /* Current list */
3077    ozone_draw_entries(
3078          ozone,
3079          p_disp,
3080          p_anim,
3081          settings,
3082          userdata,
3083          video_width,
3084          video_height,
3085          (unsigned)ozone->selection,
3086          (unsigned)ozone->selection_old,
3087          menu_entries_get_selection_buf_ptr(0),
3088          ozone->animations.list_alpha,
3089          ozone->animations.scroll_y,
3090          ozone->is_playlist
3091          );
3092 
3093    /* Old list */
3094    if (ozone->draw_old_list)
3095       ozone_draw_entries(
3096             ozone,
3097             p_disp,
3098             p_anim,
3099             settings,
3100             userdata,
3101             video_width,
3102             video_height,
3103             (unsigned)ozone->selection_old_list,
3104             (unsigned)ozone->selection_old_list,
3105             &ozone->selection_buf_old,
3106             ozone->animations.list_alpha,
3107             ozone->scroll_old,
3108             ozone->is_playlist_old
3109       );
3110 
3111    /* Thumbnail bar */
3112    if (ozone->show_thumbnail_bar)
3113       ozone_draw_thumbnail_bar(ozone,
3114             p_disp,
3115             p_anim,
3116             settings,
3117             userdata,
3118             video_width,
3119             video_height,
3120             libretro_running,
3121             menu_framebuffer_opacity);
3122 
3123    if (dispctx && dispctx->scissor_end)
3124       dispctx->scissor_end(userdata,
3125             video_width, video_height);
3126 
3127    /* Flush first layer of text */
3128    ozone_font_flush(video_width, video_height, &ozone->fonts.footer);
3129    ozone_font_flush(video_width, video_height, &ozone->fonts.title);
3130    ozone_font_flush(video_width, video_height, &ozone->fonts.time);
3131    ozone_font_flush(video_width, video_height, &ozone->fonts.entries_label);
3132    ozone_font_flush(video_width, video_height, &ozone->fonts.entries_sublabel);
3133    ozone_font_flush(video_width, video_height, &ozone->fonts.sidebar);
3134 
3135    /* Draw fullscreen thumbnails, if required */
3136    ozone_draw_fullscreen_thumbnails(ozone,
3137          userdata,
3138          video_info->disp_userdata,
3139          video_width,
3140          video_height);
3141 
3142    /* Message box & OSK - second layer of text */
3143    if (ozone->should_draw_messagebox || draw_osk)
3144    {
3145       /* Fade in animation */
3146       if (ozone->messagebox_state_old != ozone->messagebox_state && ozone->messagebox_state)
3147       {
3148          ozone->messagebox_state_old = ozone->messagebox_state;
3149 
3150          gfx_animation_kill_by_tag(&messagebox_tag);
3151          ozone->animations.messagebox_alpha = 0.0f;
3152 
3153          entry.cb = NULL;
3154          entry.duration = ANIMATION_PUSH_ENTRY_DURATION;
3155          entry.easing_enum = EASING_OUT_QUAD;
3156          entry.subject = &ozone->animations.messagebox_alpha;
3157          entry.tag = messagebox_tag;
3158          entry.target_value = 1.0f;
3159          entry.userdata = NULL;
3160 
3161          gfx_animation_push(&entry);
3162       }
3163       /* Fade out animation */
3164       else if (ozone->messagebox_state_old != ozone->messagebox_state && !ozone->messagebox_state)
3165       {
3166          ozone->messagebox_state_old = ozone->messagebox_state;
3167          ozone->messagebox_state = false;
3168 
3169          gfx_animation_kill_by_tag(&messagebox_tag);
3170          ozone->animations.messagebox_alpha = 1.0f;
3171 
3172          entry.cb = ozone_messagebox_fadeout_cb;
3173          entry.duration = ANIMATION_PUSH_ENTRY_DURATION;
3174          entry.easing_enum = EASING_OUT_QUAD;
3175          entry.subject = &ozone->animations.messagebox_alpha;
3176          entry.tag = messagebox_tag;
3177          entry.target_value = 0.0f;
3178          entry.userdata = ozone;
3179 
3180          gfx_animation_push(&entry);
3181       }
3182 
3183       ozone_draw_backdrop(
3184             userdata,
3185             video_info->disp_userdata,
3186             video_width,
3187             video_height,
3188             float_min(ozone->animations.messagebox_alpha, 0.75f));
3189 
3190       if (draw_osk)
3191       {
3192          const char *label = menu_input_dialog_get_label_buffer();
3193          const char *str   = menu_input_dialog_get_buffer();
3194 
3195          ozone_draw_osk(ozone,
3196                userdata,
3197                video_info->disp_userdata,
3198                video_width,
3199                video_height,
3200                label, str);
3201       }
3202       else
3203          ozone_draw_messagebox(
3204                ozone,
3205                p_disp,
3206                userdata,
3207                video_width,
3208                video_height,
3209                ozone->pending_message);
3210 
3211       /* Flush second layer of text */
3212       ozone_font_flush(video_width, video_height, &ozone->fonts.footer);
3213       ozone_font_flush(video_width, video_height, &ozone->fonts.entries_label);
3214    }
3215 
3216    /* Cursor */
3217    if (ozone->show_cursor && (ozone->pointer.type != MENU_POINTER_DISABLED))
3218    {
3219       bool cursor_visible = (video_fullscreen || mouse_grabbed) &&
3220             menu_mouse_enable;
3221 
3222       gfx_display_set_alpha(ozone->pure_white, 1.0f);
3223       if (cursor_visible)
3224          gfx_display_draw_cursor(
3225                p_disp,
3226                userdata,
3227                video_width,
3228                video_height,
3229                cursor_visible,
3230                ozone->pure_white,
3231                ozone->dimensions.cursor_size,
3232                ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_POINTER],
3233                ozone->pointer.x,
3234                ozone->pointer.y,
3235                video_width,
3236                video_height
3237                );
3238    }
3239 
3240    /* Unbind fonts */
3241    ozone_font_unbind(&ozone->fonts.footer);
3242    ozone_font_unbind(&ozone->fonts.title);
3243    ozone_font_unbind(&ozone->fonts.time);
3244    ozone_font_unbind(&ozone->fonts.entries_label);
3245    ozone_font_unbind(&ozone->fonts.entries_sublabel);
3246    ozone_font_unbind(&ozone->fonts.sidebar);
3247 
3248    video_driver_set_viewport(video_width, video_height, false, true);
3249 }
3250 
ozone_set_header(ozone_handle_t * ozone)3251 static void ozone_set_header(ozone_handle_t *ozone)
3252 {
3253    if (ozone->categories_selection_ptr <= ozone->system_tab_end)
3254       menu_entries_get_title(ozone->title, sizeof(ozone->title));
3255    else
3256    {
3257       ozone_node_t *node = (ozone_node_t*)file_list_get_userdata_at_offset(&ozone->horizontal_list, ozone->categories_selection_ptr - ozone->system_tab_end-1);
3258 
3259       if (node && node->console_name)
3260       {
3261          strlcpy(ozone->title, node->console_name, sizeof(ozone->title));
3262 
3263          /* Add current search terms */
3264          menu_entries_search_append_terms_string(
3265                ozone->title, sizeof(ozone->title));
3266       }
3267    }
3268 }
3269 
ozone_animation_end(void * userdata)3270 static void ozone_animation_end(void *userdata)
3271 {
3272    ozone_handle_t *ozone = (ozone_handle_t*) userdata;
3273    ozone->draw_old_list             = false;
3274    ozone->animations.cursor_alpha   = 1.0f;
3275 }
3276 
ozone_list_open(ozone_handle_t * ozone,settings_t * settings)3277 static void ozone_list_open(ozone_handle_t *ozone, settings_t *settings)
3278 {
3279    struct gfx_animation_ctx_entry entry;
3280    uintptr_t sidebar_tag        = (uintptr_t)&ozone->sidebar_offset;
3281 
3282    ozone->draw_old_list         = true;
3283 
3284    /* Left/right animation */
3285    ozone->animations.list_alpha = 0.0f;
3286 
3287    entry.cb                     = ozone_animation_end;
3288    entry.duration               = ANIMATION_PUSH_ENTRY_DURATION;
3289    entry.easing_enum            = EASING_OUT_QUAD;
3290    entry.subject                = &ozone->animations.list_alpha;
3291    entry.tag                    = (uintptr_t)NULL;
3292    entry.target_value           = 1.0f;
3293    entry.userdata               = ozone;
3294 
3295    gfx_animation_push(&entry);
3296 
3297    /* Sidebar animation */
3298    ozone_sidebar_update_collapse(ozone, settings, true);
3299 
3300    /* Kill any existing sidebar slide-in/out animations
3301     * before pushing a new one
3302     * > This is required since the 'ozone_collapse_end'
3303     *   callback from an unfinished slide-out animation
3304     *   may subsequently override the 'draw_sidebar'
3305     *   value set at the beginning of the next slide-in
3306     *   animation... */
3307    gfx_animation_kill_by_tag(&sidebar_tag);
3308 
3309    if (ozone->depth == 1)
3310    {
3311       ozone->draw_sidebar = true;
3312 
3313       entry.cb            = NULL;
3314       entry.duration      = ANIMATION_PUSH_ENTRY_DURATION;
3315       entry.easing_enum   = EASING_OUT_QUAD;
3316       entry.subject       = &ozone->sidebar_offset;
3317       entry.tag           = sidebar_tag;
3318       entry.target_value  = 0.0f;
3319       entry.userdata      = NULL;
3320 
3321       gfx_animation_push(&entry);
3322    }
3323    else if (ozone->depth > 1)
3324    {
3325       struct gfx_animation_ctx_entry entry;
3326 
3327       entry.cb           = ozone_collapse_end;
3328       entry.duration     = ANIMATION_PUSH_ENTRY_DURATION;
3329       entry.easing_enum  = EASING_OUT_QUAD;
3330       entry.subject      = &ozone->sidebar_offset;
3331       entry.tag          = sidebar_tag;
3332       entry.target_value = -ozone->dimensions_sidebar_width;
3333       entry.userdata     = (void*)ozone;
3334 
3335       gfx_animation_push(&entry);
3336    }
3337 }
3338 
ozone_populate_entries(void * data,const char * path,const char * label,unsigned k)3339 static void ozone_populate_entries(void *data,
3340       const char *path, const char *label, unsigned k)
3341 {
3342    settings_t *settings  = NULL;
3343    ozone_handle_t *ozone = (ozone_handle_t*) data;
3344 
3345    int new_depth;
3346    bool animate;
3347 
3348    if (!ozone)
3349       return;
3350 
3351    settings              = config_get_ptr();
3352 
3353    ozone_set_header(ozone);
3354 
3355    if (menu_driver_ctl(RARCH_MENU_CTL_IS_PREVENT_POPULATE, NULL))
3356    {
3357       menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL);
3358       ozone_selection_changed(ozone, false);
3359 
3360       /* Handle playlist searches
3361        * (Ozone is a fickle beast...) */
3362       if (ozone->is_playlist)
3363       {
3364          menu_serch_terms_t *menu_search_terms =
3365                menu_entries_search_get_terms();
3366          size_t num_search_terms               =
3367                menu_search_terms ? menu_search_terms->size : 0;
3368 
3369          if (ozone->num_search_terms_old != num_search_terms)
3370          {
3371             /* Refresh thumbnails */
3372             ozone_unload_thumbnail_textures(ozone);
3373 
3374             if (gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
3375                 gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
3376             {
3377                ozone_set_thumbnail_content(ozone, "");
3378                ozone_update_thumbnail_image(ozone);
3379             }
3380 
3381             /* If we are currently inside an empty
3382              * playlist, return to the sidebar */
3383             if (!ozone->cursor_in_sidebar)
3384             {
3385                file_list_t *list       = menu_entries_get_selection_buf_ptr(0);
3386                uintptr_t animation_tag = (uintptr_t)&ozone->animations.cursor_alpha;
3387                bool goto_sidebar       = false;
3388 
3389                if (!list || (list->size < 1))
3390                   goto_sidebar = true;
3391 
3392                if (!goto_sidebar &&
3393                    (list->list[0].type != FILE_TYPE_RPL_ENTRY))
3394                   goto_sidebar = true;
3395 
3396                if (goto_sidebar)
3397                {
3398                   gfx_animation_kill_by_tag(&animation_tag);
3399                   ozone->empty_playlist = true;
3400                   ozone_go_to_sidebar(ozone, settings, animation_tag);
3401                }
3402             }
3403 
3404             ozone->num_search_terms_old = num_search_terms;
3405          }
3406       }
3407 
3408       return;
3409    }
3410 
3411    ozone->need_compute = true;
3412 
3413    ozone->first_onscreen_entry    = 0;
3414    ozone->last_onscreen_entry     = 0;
3415 
3416    new_depth = (int)ozone_list_get_size(ozone, MENU_LIST_PLAIN);
3417 
3418    animate                    = new_depth != ozone->depth;
3419    ozone->fade_direction      = new_depth <= ozone->depth;
3420    ozone->depth               = new_depth;
3421    ozone->is_playlist         = ozone_is_playlist(ozone, true);
3422    ozone->is_db_manager_list  = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DATABASE_MANAGER_LIST));
3423    ozone->is_file_list        = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES));
3424    ozone->is_quick_menu       = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS)) ||
3425                                 string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENT_SETTINGS));
3426 
3427    if (animate)
3428       if (ozone->categories_selection_ptr == ozone->categories_active_idx_old)
3429          ozone_list_open(ozone, settings);
3430 
3431    /* Thumbnails
3432     * > Note: Leave current thumbnails loaded when
3433     *   opening the quick menu - allows proper fade
3434     *   out of the fullscreen thumbnail viewer */
3435    if (!ozone->is_quick_menu)
3436    {
3437       ozone_unload_thumbnail_textures(ozone);
3438 
3439       if (gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
3440           gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
3441       {
3442          /* Only auto-load thumbnails if we are viewing
3443           * a playlist or a database manager list
3444           * > Note that we can ignore file browser lists,
3445           *   since the first selected item on such a list
3446           *   can never have a thumbnail */
3447          if (ozone->is_playlist ||
3448              (ozone->depth == 4 && ozone->is_db_manager_list))
3449          {
3450             ozone_set_thumbnail_content(ozone, "");
3451             ozone_update_thumbnail_image(ozone);
3452          }
3453       }
3454    }
3455 
3456    /* Fullscreen thumbnails are only enabled on
3457     * playlists, database manager lists and file
3458     * lists */
3459    ozone->fullscreen_thumbnails_available =
3460          (ozone->is_playlist        && ozone->depth == 1) ||
3461          (ozone->is_db_manager_list && ozone->depth == 4) ||
3462           ozone->is_file_list;
3463 }
3464 
3465 /* TODO: Fancy toggle animation */
3466 
ozone_toggle(void * userdata,bool menu_on)3467 static void ozone_toggle(void *userdata, bool menu_on)
3468 {
3469    settings_t *settings  = NULL;
3470    bool tmp              = false;
3471    ozone_handle_t *ozone = (ozone_handle_t*) userdata;
3472 
3473    if (!ozone)
3474       return;
3475 
3476    settings              = config_get_ptr();
3477    tmp                   = !menu_entries_ctl(
3478          MENU_ENTRIES_CTL_NEEDS_REFRESH, NULL);
3479 
3480    if (tmp)
3481       menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL);
3482    else
3483       menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL);
3484 
3485    if (ozone->depth == 1)
3486    {
3487       ozone->draw_sidebar = true;
3488       ozone->sidebar_offset = 0.0f;
3489    }
3490 
3491    ozone_sidebar_update_collapse(ozone, settings, false);
3492 }
3493 
ozone_menu_init_list(void * data)3494 static bool ozone_menu_init_list(void *data)
3495 {
3496    menu_displaylist_info_t info;
3497 
3498    settings_t *settings         = config_get_ptr();
3499    file_list_t *menu_stack      = menu_entries_get_menu_stack_ptr(0);
3500    file_list_t *selection_buf   = menu_entries_get_selection_buf_ptr(0);
3501 
3502    menu_displaylist_info_init(&info);
3503 
3504    info.label                   = strdup(
3505          msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU));
3506    info.exts                    = strdup("lpl");
3507    info.type_default            = FILE_TYPE_PLAIN;
3508    info.enum_idx                = MENU_ENUM_LABEL_MAIN_MENU;
3509 
3510    menu_entries_append_enum(menu_stack, info.path,
3511          info.label,
3512          MENU_ENUM_LABEL_MAIN_MENU,
3513          info.type, info.flags, 0);
3514 
3515    info.list  = selection_buf;
3516 
3517    if (!menu_displaylist_ctl(DISPLAYLIST_MAIN_MENU, &info, settings))
3518       goto error;
3519 
3520    info.need_push = true;
3521 
3522    if (!menu_displaylist_process(&info))
3523       goto error;
3524 
3525    menu_displaylist_info_free(&info);
3526    return true;
3527 
3528 error:
3529    menu_displaylist_info_free(&info);
3530    return false;
3531 }
3532 
ozone_copy_node(const ozone_node_t * old_node)3533 static ozone_node_t *ozone_copy_node(const ozone_node_t *old_node)
3534 {
3535    ozone_node_t *new_node = (ozone_node_t*)malloc(sizeof(*new_node));
3536 
3537    *new_node            = *old_node;
3538    new_node->fullpath   = old_node->fullpath ? strdup(old_node->fullpath) : NULL;
3539 
3540    return new_node;
3541 }
3542 
ozone_list_insert(void * userdata,file_list_t * list,const char * path,const char * fullpath,const char * label,size_t list_size,unsigned type)3543 static void ozone_list_insert(void *userdata,
3544       file_list_t *list,
3545       const char *path,
3546       const char *fullpath,
3547       const char *label,
3548       size_t list_size,
3549       unsigned type)
3550 {
3551    ozone_handle_t *ozone = (ozone_handle_t*) userdata;
3552    ozone_node_t *node = NULL;
3553    int i = (int)list_size;
3554 
3555    if (!ozone || !list)
3556       return;
3557 
3558    ozone->need_compute = true;
3559 
3560    node = (ozone_node_t*)list->list[i].userdata;
3561 
3562    if (!node)
3563       node = ozone_alloc_node();
3564 
3565    if (!node)
3566    {
3567       RARCH_ERR("ozone node could not be allocated.\n");
3568       return;
3569    }
3570 
3571    if (!string_is_empty(fullpath))
3572    {
3573       if (node->fullpath)
3574          free(node->fullpath);
3575 
3576       node->fullpath = strdup(fullpath);
3577    }
3578 
3579    list->list[i].userdata = node;
3580 }
3581 
ozone_list_deep_copy(const file_list_t * src,file_list_t * dst,size_t first,size_t last)3582 static void ozone_list_deep_copy(const file_list_t *src, file_list_t *dst,
3583       size_t first, size_t last)
3584 {
3585    size_t i, j   = 0;
3586    uintptr_t tag = (uintptr_t)dst;
3587 
3588    gfx_animation_kill_by_tag(&tag);
3589 
3590    ozone_free_list_nodes(dst, true);
3591 
3592    file_list_clear(dst);
3593    file_list_reserve(dst, (last + 1) - first);
3594 
3595    for (i = first; i <= last; ++i)
3596    {
3597       struct item_file *d = &dst->list[j];
3598       struct item_file *s = &src->list[i];
3599       void     *src_udata = s->userdata;
3600       void     *src_adata = s->actiondata;
3601 
3602       *d       = *s;
3603       d->alt   = string_is_empty(d->alt)   ? NULL : strdup(d->alt);
3604       d->path  = string_is_empty(d->path)  ? NULL : strdup(d->path);
3605       d->label = string_is_empty(d->label) ? NULL : strdup(d->label);
3606 
3607       if (src_udata)
3608          dst->list[j].userdata = (void*)ozone_copy_node((const ozone_node_t*)src_udata);
3609 
3610       if (src_adata)
3611       {
3612          void *data = malloc(sizeof(menu_file_list_cbs_t));
3613          memcpy(data, src_adata, sizeof(menu_file_list_cbs_t));
3614          dst->list[j].actiondata = data;
3615       }
3616 
3617       ++j;
3618    }
3619 
3620    dst->size = j;
3621 }
3622 
ozone_list_cache(void * data,enum menu_list_type type,unsigned action)3623 void ozone_list_cache(void *data,
3624       enum menu_list_type type, unsigned action)
3625 {
3626    size_t y, entries_end;
3627    unsigned i;
3628    unsigned video_info_height;
3629    float bottom_boundary;
3630    ozone_node_t *first_node;
3631    float scale_factor;
3632    unsigned first             = 0;
3633    unsigned last              = 0;
3634    file_list_t *selection_buf = NULL;
3635    ozone_handle_t *ozone      = (ozone_handle_t*)data;
3636 
3637    if (!ozone)
3638       return;
3639 
3640    scale_factor               = ozone->last_scale_factor;
3641    ozone->need_compute        = true;
3642    ozone->selection_old_list  = ozone->selection;
3643    ozone->scroll_old          = ozone->animations.scroll_y;
3644    ozone->is_playlist_old     = ozone->is_playlist;
3645 
3646    /* Deep copy visible elements */
3647    video_driver_get_size(NULL, &video_info_height);
3648    y                          = ozone->dimensions.header_height + ozone->dimensions.entry_padding_vertical;
3649    entries_end                = menu_entries_get_size();
3650    selection_buf              = menu_entries_get_selection_buf_ptr(0);
3651    bottom_boundary            = video_info_height - ozone->dimensions.header_height - ozone->dimensions.footer_height;
3652 
3653    for (i = 0; i < entries_end; i++)
3654    {
3655       ozone_node_t *node = (ozone_node_t*)selection_buf->list[i].userdata;
3656 
3657       if (!node)
3658          continue;
3659 
3660       if (y + ozone->animations.scroll_y + node->height + 20 * scale_factor < ozone->dimensions.header_height + ozone->dimensions.entry_padding_vertical)
3661       {
3662          first++;
3663          goto text_iterate;
3664       }
3665       else if (y + ozone->animations.scroll_y - node->height - 20 * scale_factor > bottom_boundary)
3666          goto text_iterate;
3667 
3668       last++;
3669 text_iterate:
3670       y += node->height;
3671    }
3672 
3673    last                    -= 1;
3674    last                    += first;
3675 
3676    first_node               = (ozone_node_t*)selection_buf->list[first].userdata;
3677    ozone->old_list_offset_y = first_node->position_y;
3678 
3679    ozone_list_deep_copy(selection_buf,
3680          &ozone->selection_buf_old, first, last);
3681 }
3682 
ozone_environ_cb(enum menu_environ_cb type,void * data,void * userdata)3683 static int ozone_environ_cb(enum menu_environ_cb type, void *data, void *userdata)
3684 {
3685    ozone_handle_t *ozone = (ozone_handle_t*) userdata;
3686 
3687    if (!ozone)
3688       return -1;
3689 
3690    switch (type)
3691    {
3692       case MENU_ENVIRON_ENABLE_MOUSE_CURSOR:
3693          ozone->show_cursor = true;
3694          break;
3695       case MENU_ENVIRON_DISABLE_MOUSE_CURSOR:
3696          ozone->show_cursor = false;
3697          break;
3698       case MENU_ENVIRON_RESET_HORIZONTAL_LIST:
3699          if (!ozone)
3700             return -1;
3701          {
3702             settings_t *settings              = config_get_ptr();
3703             ozone_refresh_horizontal_list(ozone, settings);
3704          }
3705          break;
3706       case MENU_ENVIRON_ENABLE_SCREENSAVER:
3707          ozone->show_screensaver = true;
3708          break;
3709       case MENU_ENVIRON_DISABLE_SCREENSAVER:
3710          ozone->show_screensaver = false;
3711          break;
3712       default:
3713          return -1;
3714    }
3715 
3716    return 0;
3717 }
3718 
ozone_messagebox(void * data,const char * message)3719 static void ozone_messagebox(void *data, const char *message)
3720 {
3721    ozone_handle_t *ozone = (ozone_handle_t*) data;
3722 
3723    if (!ozone || string_is_empty(message))
3724       return;
3725 
3726    if (ozone->pending_message)
3727    {
3728       free(ozone->pending_message);
3729       ozone->pending_message = NULL;
3730    }
3731 
3732    ozone->pending_message        = strdup(message);
3733    ozone->messagebox_state       = true;
3734    ozone->should_draw_messagebox = true;
3735 }
3736 
ozone_deferred_push_content_actions(menu_displaylist_info_t * info)3737 static int ozone_deferred_push_content_actions(menu_displaylist_info_t *info)
3738 {
3739    settings_t *settings         = config_get_ptr();
3740    if (!menu_displaylist_ctl(
3741             DISPLAYLIST_HORIZONTAL_CONTENT_ACTIONS, info, settings))
3742       return -1;
3743    menu_displaylist_process(info);
3744    menu_displaylist_info_free(info);
3745    return 0;
3746 }
3747 
ozone_list_bind_init_compare_label(menu_file_list_cbs_t * cbs)3748 static int ozone_list_bind_init_compare_label(menu_file_list_cbs_t *cbs)
3749 {
3750    if (cbs && cbs->enum_idx != MSG_UNKNOWN)
3751    {
3752       switch (cbs->enum_idx)
3753       {
3754          case MENU_ENUM_LABEL_CONTENT_ACTIONS:
3755             cbs->action_deferred_push = ozone_deferred_push_content_actions;
3756             break;
3757          default:
3758             return -1;
3759       }
3760    }
3761 
3762    return 0;
3763 }
3764 
ozone_list_bind_init(menu_file_list_cbs_t * cbs,const char * path,const char * label,unsigned type,size_t idx)3765 static int ozone_list_bind_init(menu_file_list_cbs_t *cbs,
3766       const char *path, const char *label, unsigned type, size_t idx)
3767 {
3768    if (ozone_list_bind_init_compare_label(cbs) == 0)
3769       return 0;
3770 
3771    return -1;
3772 }
3773 
ozone_pointer_up(void * userdata,unsigned x,unsigned y,unsigned ptr,enum menu_input_pointer_gesture gesture,menu_file_list_cbs_t * cbs,menu_entry_t * entry,unsigned action)3774 static int ozone_pointer_up(void *userdata,
3775       unsigned x, unsigned y, unsigned ptr,
3776       enum menu_input_pointer_gesture gesture,
3777       menu_file_list_cbs_t *cbs,
3778       menu_entry_t *entry, unsigned action)
3779 {
3780    unsigned width, height;
3781    ozone_handle_t *ozone             = (ozone_handle_t*)userdata;
3782    file_list_t *selection_buf        = menu_entries_get_selection_buf_ptr(0);
3783    uintptr_t sidebar_tag             = (uintptr_t)selection_buf;
3784    size_t selection                  = menu_navigation_get_selection();
3785    size_t entries_end                = menu_entries_get_size();
3786    settings_t *settings              = config_get_ptr();
3787 
3788    if (!ozone)
3789       return -1;
3790 
3791    /* If fullscreen thumbnail view is enabled,
3792     * all input will disable it and otherwise
3793     * be ignored */
3794    if (ozone->show_fullscreen_thumbnails)
3795    {
3796       /* Must reset scroll acceleration, in case
3797        * user performed a swipe (don't want menu
3798        * list to 'drift' after hiding fullscreen
3799        * thumbnails...) */
3800       menu_input_set_pointer_y_accel(0.0f);
3801 
3802       ozone_hide_fullscreen_thumbnails(ozone, true);
3803       return 0;
3804    }
3805 
3806    video_driver_get_size(&width, &height);
3807 
3808    switch (gesture)
3809    {
3810       case MENU_INPUT_GESTURE_TAP:
3811       case MENU_INPUT_GESTURE_SHORT_PRESS:
3812          /* Tap/press header or footer: Menu back/cancel */
3813          if ((y < ozone->dimensions.header_height) ||
3814              (y > height - ozone->dimensions.footer_height))
3815             return ozone_menu_entry_action(ozone, entry, selection, MENU_ACTION_CANCEL);
3816          /* Tap/press entries: Activate and/or select item */
3817          else if ((ptr < entries_end) &&
3818                   (x > ozone->dimensions_sidebar_width
3819                    + ozone->sidebar_offset) &&
3820                   (x < width - ozone->animations.thumbnail_bar_position))
3821          {
3822             if (gesture == MENU_INPUT_GESTURE_TAP)
3823             {
3824                /* A 'tap' always produces a menu action */
3825 
3826                /* If current 'pointer' item is not active,
3827                 * activate it immediately */
3828                if (ptr != selection)
3829                   menu_navigation_set_selection(ptr);
3830 
3831                /* If we are not currently in the sidebar,
3832                 * perform a MENU_ACTION_SELECT on currently
3833                 * active item
3834                 * > NOTE 1: Cannot perform a 'leave sidebar' operation
3835                 *   and a MENU_ACTION_SELECT at the same time...
3836                 * > NOTE 2: We still use 'selection' (i.e. old selection
3837                 *   value) here. This ensures that ozone_menu_entry_action()
3838                 *   registers any change due to the above automatic
3839                 *   'pointer item' activation, and thus operates
3840                 *   on the correct target entry */
3841                if (!ozone->cursor_in_sidebar)
3842                   return ozone_menu_entry_action(ozone, entry,
3843                         selection, MENU_ACTION_SELECT);
3844 
3845                /* If we currently in the sidebar, leave it */
3846                ozone_leave_sidebar(ozone, settings, sidebar_tag);
3847             }
3848             else
3849             {
3850                /* A 'short' press is used only to activate (highlight)
3851                 * an item - it does not invoke a MENU_ACTION_SELECT
3852                 * action */
3853                menu_input_set_pointer_y_accel(0.0f);
3854 
3855                if (ptr != selection)
3856                   menu_navigation_set_selection(ptr);
3857 
3858                /* If we are currently in the sidebar, leave it */
3859                if (ozone->cursor_in_sidebar)
3860                   ozone_leave_sidebar(ozone, settings, sidebar_tag);
3861                /* If this is a playlist and the selection
3862                 * has changed, must update thumbnails */
3863                else if (ozone->is_playlist &&
3864                         (ozone->depth == 1) &&
3865                         (ptr != selection))
3866                {
3867                   ozone_set_thumbnail_content(ozone, "");
3868                   ozone_update_thumbnail_image(ozone);
3869                }
3870             }
3871          }
3872          /* Tap/press thumbnail bar: toggle content metadata
3873           * override */
3874          else if (x > width - ozone->animations.thumbnail_bar_position)
3875          {
3876             /* Want to capture all input here, but only act
3877              * upon it if the content metadata toggle is
3878              * available (i.e. viewing a playlist with dual
3879              * thumbnails) */
3880             if (ozone_metadata_override_available(ozone))
3881                return ozone_menu_entry_action(ozone, entry, selection, MENU_ACTION_INFO);
3882          }
3883          /* Tap/press sidebar: return to sidebar or select
3884           * category */
3885          else if (ozone->pointer_in_sidebar)
3886          {
3887             /* If cursor is not in sidebar, return to sidebar */
3888             if (!ozone->cursor_in_sidebar)
3889                ozone_go_to_sidebar(ozone, settings, sidebar_tag);
3890             /* Otherwise, select current category */
3891             else if (
3892                      ozone->pointer_categories_selection
3893                   != ozone->categories_selection_ptr)
3894             {
3895                unsigned horizontal_list_size = (unsigned)ozone->horizontal_list.size;
3896 
3897                /* Ensure that current category is valid */
3898                if (ozone->pointer_categories_selection <= ozone->system_tab_end + horizontal_list_size)
3899                   ozone_sidebar_goto(ozone, (unsigned)ozone->pointer_categories_selection);
3900             }
3901          }
3902          break;
3903       case MENU_INPUT_GESTURE_LONG_PRESS:
3904          /* 'Reset to default' action */
3905          if ((y > ozone->dimensions.header_height) &&
3906              (y < height - ozone->dimensions.footer_height) &&
3907              (ptr < entries_end) &&
3908              (ptr == selection) &&
3909              (x > ozone->dimensions_sidebar_width + ozone->sidebar_offset) &&
3910              (x < width - ozone->animations.thumbnail_bar_position))
3911             return ozone_menu_entry_action(ozone,
3912                   entry, selection, MENU_ACTION_START);
3913          break;
3914       case MENU_INPUT_GESTURE_SWIPE_LEFT:
3915          /* If this is a playlist, descend alphabet
3916           * > Note: Can only do this if we are not using
3917           *   a mouse, since it conflicts with auto selection
3918           *   of entry under cursor */
3919          if ((ozone->pointer.type != MENU_POINTER_MOUSE) &&
3920              ozone->is_playlist &&
3921              (ozone->depth == 1))
3922             return ozone_menu_entry_action(ozone, entry, (size_t)ptr, MENU_ACTION_SCROLL_UP);
3923          break;
3924       case MENU_INPUT_GESTURE_SWIPE_RIGHT:
3925          /* If this is a playlist, ascend alphabet
3926           * > Note: Can only do this if we are not using
3927           *   a mouse, since it conflicts with auto selection
3928           *   of entry under cursor */
3929          if ((ozone->pointer.type != MENU_POINTER_MOUSE) &&
3930              ozone->is_playlist &&
3931              (ozone->depth == 1))
3932             return ozone_menu_entry_action(ozone, entry, (size_t)ptr, MENU_ACTION_SCROLL_DOWN);
3933          break;
3934       default:
3935          /* Ignore input */
3936          break;
3937    }
3938 
3939    return 0;
3940 }
3941 
ozone_alloc_node(void)3942 ozone_node_t *ozone_alloc_node(void)
3943 {
3944    ozone_node_t *node   = (ozone_node_t*)malloc(sizeof(*node));
3945 
3946    node->height         = 0;
3947    node->position_y     = 0;
3948    node->console_name   = NULL;
3949    node->icon           = 0;
3950    node->content_icon   = 0;
3951    node->fullpath       = NULL;
3952    node->sublabel_lines = 0;
3953    node->wrap           = false;
3954 
3955    return node;
3956 }
3957 
ozone_list_get_size(void * data,enum menu_list_type type)3958 size_t ozone_list_get_size(void *data, enum menu_list_type type)
3959 {
3960    ozone_handle_t *ozone = (ozone_handle_t*) data;
3961 
3962    if (!ozone)
3963       return 0;
3964 
3965    switch (type)
3966    {
3967       case MENU_LIST_PLAIN:
3968          return menu_entries_get_stack_size(0);
3969       case MENU_LIST_HORIZONTAL:
3970          return ozone->horizontal_list.size;
3971       case MENU_LIST_TABS:
3972          return ozone->system_tab_end;
3973    }
3974 
3975    return 0;
3976 }
3977 
ozone_free_list_nodes(file_list_t * list,bool actiondata)3978 void ozone_free_list_nodes(file_list_t *list, bool actiondata)
3979 {
3980    unsigned i, size = list ? (unsigned)list->size : 0;
3981 
3982    for (i = 0; i < size; ++i)
3983    {
3984       ozone_free_node((ozone_node_t*)file_list_get_userdata_at_offset(list, i));
3985 
3986       list->list[i].userdata = NULL;
3987 
3988       if (actiondata)
3989          file_list_free_actiondata(list, i);
3990    }
3991 }
3992 
ozone_update_content_metadata(ozone_handle_t * ozone)3993 void ozone_update_content_metadata(ozone_handle_t *ozone)
3994 {
3995    const char *core_name             = NULL;
3996    size_t selection                  = menu_navigation_get_selection();
3997    playlist_t *playlist              = playlist_get_cached();
3998    settings_t *settings              = config_get_ptr();
3999    bool scroll_content_metadata      = settings->bools.ozone_scroll_content_metadata;
4000    bool show_entry_idx               = settings->bools.playlist_show_entry_idx;
4001    bool content_runtime_log          = settings->bools.content_runtime_log;
4002    bool content_runtime_log_aggr     = settings->bools.content_runtime_log_aggregate;
4003    const char *directory_runtime_log = settings->paths.directory_runtime_log;
4004    const char *directory_playlist    = settings->paths.directory_playlist;
4005    unsigned runtime_type             = settings->uints.playlist_sublabel_runtime_type;
4006    enum playlist_sublabel_last_played_style_type
4007          runtime_last_played_style   =
4008                (enum playlist_sublabel_last_played_style_type)
4009                      settings->uints.playlist_sublabel_last_played_style;
4010    enum playlist_sublabel_last_played_date_separator_type
4011          runtime_date_separator      =
4012                (enum playlist_sublabel_last_played_date_separator_type)
4013                      settings->uints.menu_timedate_date_separator;
4014 
4015    /* Must check whether core corresponds to 'viewer'
4016     * content even when not using a playlist, otherwise
4017     * file browser image updates are mishandled */
4018    if (gfx_thumbnail_get_core_name(ozone->thumbnail_path_data, &core_name))
4019       ozone->selection_core_is_viewer = string_is_equal(core_name, "imageviewer")
4020             || string_is_equal(core_name, "musicplayer")
4021             || string_is_equal(core_name, "movieplayer");
4022    else
4023       ozone->selection_core_is_viewer = false;
4024 
4025    if (ozone->is_playlist && playlist)
4026    {
4027       const char *core_label             = NULL;
4028       const struct playlist_entry *entry = NULL;
4029       size_t list_size                   = menu_entries_get_size();
4030       file_list_t *list                  = menu_entries_get_selection_buf_ptr(0);
4031       bool playlist_valid                = false;
4032       size_t playlist_index              = selection;
4033 
4034       /* Get playlist index corresponding
4035        * to the selected entry */
4036       if (list &&
4037           (selection < list_size) &&
4038           (list->list[selection].type == FILE_TYPE_RPL_ENTRY))
4039       {
4040          playlist_valid = true;
4041          playlist_index = list->list[selection].entry_idx;
4042       }
4043 
4044       /* Fill entry enumeration */
4045       if (show_entry_idx)
4046          snprintf(ozone->selection_entry_enumeration, sizeof(ozone->selection_entry_enumeration),
4047             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_ENTRY_IDX),
4048             (unsigned long)(playlist_index + 1), (unsigned long)list_size);
4049       else
4050          ozone->selection_entry_enumeration[0] = '\0';
4051 
4052       /* Fill core name */
4053       if (!core_name || string_is_equal(core_name, "DETECT"))
4054          core_label = msg_hash_to_str(MSG_AUTODETECT);
4055       else
4056          core_label = core_name;
4057 
4058       snprintf(ozone->selection_core_name, sizeof(ozone->selection_core_name),
4059          "%s %s", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_CORE), core_label);
4060 
4061       /* Word wrap core name string, if required */
4062       if (!scroll_content_metadata)
4063       {
4064          char tmpstr[sizeof(ozone->selection_core_name)];
4065          unsigned metadata_len =
4066                (ozone->dimensions.thumbnail_bar_width - ((ozone->dimensions.sidebar_entry_icon_padding * 2) * 2)) /
4067                      ozone->fonts.footer.glyph_width;
4068 
4069          strlcpy(tmpstr, ozone->selection_core_name, sizeof(tmpstr));
4070          (ozone->word_wrap)(ozone->selection_core_name, sizeof(ozone->selection_core_name),
4071                tmpstr, metadata_len, ozone->fonts.footer.wideglyph_width, 0);
4072          ozone->selection_core_name_lines = ozone_count_lines(ozone->selection_core_name);
4073       }
4074       else
4075          ozone->selection_core_name_lines = 1;
4076 
4077       /* Fill play time if applicable */
4078       if (playlist_valid &&
4079           (content_runtime_log || content_runtime_log_aggr))
4080          playlist_get_index(playlist, playlist_index, &entry);
4081 
4082       if (entry)
4083       {
4084          if (entry->runtime_status == PLAYLIST_RUNTIME_UNKNOWN)
4085             runtime_update_playlist(
4086                   playlist, playlist_index,
4087                   directory_runtime_log,
4088                   directory_playlist,
4089                   (runtime_type == PLAYLIST_RUNTIME_PER_CORE),
4090                   runtime_last_played_style,
4091                   runtime_date_separator);
4092 
4093          if (!string_is_empty(entry->runtime_str))
4094             strlcpy(ozone->selection_playtime, entry->runtime_str, sizeof(ozone->selection_playtime));
4095          if (!string_is_empty(entry->last_played_str))
4096             strlcpy(ozone->selection_lastplayed, entry->last_played_str, sizeof(ozone->selection_lastplayed));
4097       }
4098       else
4099       {
4100          snprintf(ozone->selection_playtime, sizeof(ozone->selection_playtime), "%s %s",
4101             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_RUNTIME),
4102             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DISABLED));
4103 
4104          snprintf(ozone->selection_lastplayed, sizeof(ozone->selection_lastplayed), "%s %s",
4105             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
4106             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DISABLED));
4107       }
4108 
4109       /* Word wrap last played string, if required */
4110       if (!scroll_content_metadata)
4111       {
4112          /* Note: Have to use a fixed length of '30' here, to
4113           * avoid awkward wrapping for certain last played time
4114           * formats. Last played strings are well defined, however
4115           * (unlike core names), so this should never overflow the
4116           * side bar */
4117          char tmpstr[sizeof(ozone->selection_lastplayed)];
4118 
4119          strlcpy(tmpstr, ozone->selection_lastplayed, sizeof(tmpstr));
4120          (ozone->word_wrap)(ozone->selection_lastplayed, sizeof(ozone->selection_lastplayed), tmpstr, 30, 100, 0);
4121          ozone->selection_lastplayed_lines = ozone_count_lines(ozone->selection_lastplayed);
4122       }
4123       else
4124          ozone->selection_lastplayed_lines = 1;
4125    }
4126 }
4127 
ozone_font_flush(unsigned video_width,unsigned video_height,ozone_font_data_t * font_data)4128 void ozone_font_flush(
4129       unsigned video_width, unsigned video_height,
4130       ozone_font_data_t *font_data)
4131 {
4132    /* Flushing is slow - only do it if font
4133     * has actually been used */
4134    if (!font_data ||
4135        (font_data->raster_block.carr.coords.vertices == 0))
4136       return;
4137 
4138    font_driver_flush(video_width, video_height, font_data->font);
4139    font_data->raster_block.carr.coords.vertices = 0;
4140 }
4141 
ozone_hide_fullscreen_thumbnails(ozone_handle_t * ozone,bool animate)4142 void ozone_hide_fullscreen_thumbnails(ozone_handle_t *ozone, bool animate)
4143 {
4144    uintptr_t alpha_tag                = (uintptr_t)
4145       &ozone->animations.fullscreen_thumbnail_alpha;
4146    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
4147 
4148    /* Kill any existing fade in/out animations */
4149    gfx_animation_kill_by_tag(&alpha_tag);
4150 
4151    /* Check whether animations are enabled */
4152    if (animate && (ozone->animations.fullscreen_thumbnail_alpha > 0.0f))
4153    {
4154       gfx_animation_ctx_entry_t animation_entry;
4155 
4156       /* Configure fade out animation */
4157       animation_entry.easing_enum  = EASING_OUT_QUAD;
4158       animation_entry.tag          = alpha_tag;
4159       animation_entry.duration     = p_gfx_thumb->fade_duration;
4160       animation_entry.target_value = 0.0f;
4161       animation_entry.subject      = &ozone->animations.fullscreen_thumbnail_alpha;
4162       animation_entry.cb           = NULL;
4163       animation_entry.userdata     = NULL;
4164 
4165       /* Push animation */
4166       gfx_animation_push(&animation_entry);
4167    }
4168    /* No animation - just set thumbnail alpha to zero */
4169    else
4170       ozone->animations.fullscreen_thumbnail_alpha = 0.0f;
4171 
4172    /* Disable fullscreen thumbnails */
4173    ozone->show_fullscreen_thumbnails = false;
4174 }
4175 
ozone_show_fullscreen_thumbnails(ozone_handle_t * ozone)4176 void ozone_show_fullscreen_thumbnails(ozone_handle_t *ozone)
4177 {
4178    menu_entry_t selected_entry;
4179    gfx_animation_ctx_entry_t animation_entry;
4180    const char *thumbnail_label        = NULL;
4181    file_list_t *selection_buf         = menu_entries_get_selection_buf_ptr(0);
4182    uintptr_t alpha_tag                = (uintptr_t)&ozone->animations.fullscreen_thumbnail_alpha;
4183    uintptr_t scroll_tag               = (uintptr_t)selection_buf;
4184    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
4185 
4186    /* Before showing fullscreen thumbnails, must
4187     * ensure that any existing fullscreen thumbnail
4188     * view is disabled... */
4189    ozone_hide_fullscreen_thumbnails(ozone, false);
4190 
4191    /* Sanity check: Return immediately if this is
4192     * a menu without thumbnail support, or cursor
4193     * is currently in the sidebar */
4194    if (!ozone->fullscreen_thumbnails_available ||
4195        ozone->cursor_in_sidebar)
4196       return;
4197 
4198    /* We can only enable fullscreen thumbnails if
4199     * current selection has at least one valid thumbnail
4200     * and all thumbnails for current selection are already
4201     * loaded/available */
4202    if (ozone->selection_core_is_viewer)
4203    {
4204       /* imageviewer content requires special treatment,
4205        * since only the right thumbnail is ever loaded */
4206       if (!gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT))
4207          return;
4208 
4209       if (ozone->thumbnails.right.status != GFX_THUMBNAIL_STATUS_AVAILABLE)
4210          return;
4211    }
4212    else
4213    {
4214       bool left_thumbnail_enabled = gfx_thumbnail_is_enabled(
4215             ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT);
4216 
4217       if (!left_thumbnail_enabled &&
4218           !gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT))
4219          return;
4220 
4221       if ((ozone->thumbnails.right.status == GFX_THUMBNAIL_STATUS_AVAILABLE) &&
4222           (left_thumbnail_enabled &&
4223                ((ozone->thumbnails.left.status != GFX_THUMBNAIL_STATUS_MISSING) &&
4224                 (ozone->thumbnails.left.status != GFX_THUMBNAIL_STATUS_AVAILABLE))))
4225          return;
4226 
4227       if ((ozone->thumbnails.right.status == GFX_THUMBNAIL_STATUS_MISSING) &&
4228           (!left_thumbnail_enabled ||
4229                (ozone->thumbnails.left.status != GFX_THUMBNAIL_STATUS_AVAILABLE)))
4230          return;
4231    }
4232 
4233    /* Menu list must be stationary while fullscreen
4234     * thumbnails are shown
4235     * > Kill any existing scroll animations and
4236     *   reset scroll acceleration */
4237    gfx_animation_kill_by_tag(&scroll_tag);
4238    menu_input_set_pointer_y_accel(0.0f);
4239 
4240    /* Cache selected entry label
4241     * (used as title when fullscreen thumbnails
4242     * are shown) */
4243    ozone->fullscreen_thumbnail_label[0] = '\0';
4244 
4245    /* > Get menu entry */
4246    MENU_ENTRY_INIT(selected_entry);
4247    selected_entry.path_enabled     = false;
4248    selected_entry.value_enabled    = false;
4249    selected_entry.sublabel_enabled = false;
4250    menu_entry_get(&selected_entry, 0, (size_t)ozone->selection, NULL, true);
4251 
4252    /* > Get entry label */
4253    if (!string_is_empty(selected_entry.rich_label))
4254       thumbnail_label  = selected_entry.rich_label;
4255    else
4256       thumbnail_label  = selected_entry.path;
4257 
4258    /* > Sanity check */
4259    if (!string_is_empty(thumbnail_label))
4260       strlcpy(
4261             ozone->fullscreen_thumbnail_label,
4262             thumbnail_label,
4263             sizeof(ozone->fullscreen_thumbnail_label));
4264 
4265    /* Configure fade in animation */
4266    animation_entry.easing_enum  = EASING_OUT_QUAD;
4267    animation_entry.tag          = alpha_tag;
4268    animation_entry.duration     = p_gfx_thumb->fade_duration;
4269    animation_entry.target_value = 1.0f;
4270    animation_entry.subject      = &ozone->animations.fullscreen_thumbnail_alpha;
4271    animation_entry.cb           = NULL;
4272    animation_entry.userdata     = NULL;
4273 
4274    /* Push animation */
4275    gfx_animation_push(&animation_entry);
4276 
4277    /* Enable fullscreen thumbnails */
4278    ozone->fullscreen_thumbnail_selection = (size_t)ozone->selection;
4279    ozone->show_fullscreen_thumbnails     = true;
4280 }
4281 
ozone_toggle_metadata_override(ozone_handle_t * ozone)4282 void ozone_toggle_metadata_override(ozone_handle_t *ozone)
4283 {
4284    gfx_animation_ctx_entry_t animation_entry;
4285    uintptr_t alpha_tag                = (uintptr_t)
4286       &ozone->animations.left_thumbnail_alpha;
4287    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
4288 
4289    /* Kill any existing fade in/out animations */
4290    gfx_animation_kill_by_tag(&alpha_tag);
4291 
4292    /* Set common animation parameters */
4293    animation_entry.easing_enum = EASING_OUT_QUAD;
4294    animation_entry.tag         = alpha_tag;
4295    animation_entry.duration    = p_gfx_thumb->fade_duration;
4296    animation_entry.subject     = &ozone->animations.left_thumbnail_alpha;
4297    animation_entry.cb          = NULL;
4298    animation_entry.userdata    = NULL;
4299 
4300    /* Check whether metadata override is
4301     * currently enabled */
4302    if (ozone->force_metadata_display)
4303    {
4304       /* Thumbnail will fade in */
4305       animation_entry.target_value  = 1.0f;
4306       ozone->force_metadata_display = false;
4307    }
4308    else
4309    {
4310       /* Thumbnail will fade out */
4311       animation_entry.target_value  = 0.0f;
4312       ozone->force_metadata_display = true;
4313    }
4314 
4315    /* Push animation */
4316    gfx_animation_push(&animation_entry);
4317 }
4318 
ozone_start_cursor_wiggle(ozone_handle_t * ozone,enum menu_action direction)4319 void ozone_start_cursor_wiggle(ozone_handle_t* ozone, enum menu_action direction)
4320 {
4321    /* Don't start another wiggle animation on top of another */
4322    if (!ozone || ozone->cursor_wiggle_state.wiggling)
4323       return;
4324 
4325    /* Don't allow wiggling in invalid directions */
4326    if (!(
4327          direction == MENU_ACTION_UP ||
4328          direction == MENU_ACTION_DOWN ||
4329          direction == MENU_ACTION_LEFT ||
4330          direction == MENU_ACTION_RIGHT
4331    ))
4332       return;
4333 
4334    /* Start wiggling */
4335    ozone->cursor_wiggle_state.start_time = menu_driver_get_current_time() / 1000;
4336    ozone->cursor_wiggle_state.direction  = direction;
4337    ozone->cursor_wiggle_state.amplitude  = rand() % 15 + 10;
4338    ozone->cursor_wiggle_state.wiggling   = true;
4339 }
4340 
ozone_wiggle(ozone_handle_t * ozone,float t)4341 static int ozone_wiggle(ozone_handle_t* ozone, float t)
4342 {
4343    float a = ozone->cursor_wiggle_state.amplitude;
4344 
4345    /* Damped sine wave */
4346    float w = 0.8f;   /* period */
4347    float c = 0.35f;  /* damp factor */
4348    return roundf(a * exp(-(c * t)) * sin(w * t));
4349 }
4350 
ozone_apply_cursor_wiggle_offset(ozone_handle_t * ozone,int * x,size_t * y)4351 void ozone_apply_cursor_wiggle_offset(ozone_handle_t* ozone, int* x, size_t* y)
4352 {
4353    retro_time_t cur_time;
4354    retro_time_t t;
4355 
4356    /* Don't do anything if we are not wiggling */
4357    if (!ozone || !ozone->cursor_wiggle_state.wiggling)
4358       return;
4359 
4360    cur_time = menu_driver_get_current_time() / 1000;
4361    t        = (cur_time - ozone->cursor_wiggle_state.start_time) / 10;
4362 
4363    /* Has the animation ended? */
4364    if (t >= OZONE_WIGGLE_DURATION)
4365    {
4366       ozone->cursor_wiggle_state.wiggling = false;
4367       return;
4368    }
4369 
4370    /* Change cursor position depending on wiggle direction */
4371    switch (ozone->cursor_wiggle_state.direction)
4372    {
4373       case MENU_ACTION_RIGHT:
4374          *x += ozone_wiggle(ozone, t);
4375          break;
4376       case MENU_ACTION_LEFT:
4377          *x -= ozone_wiggle(ozone, t);
4378          break;
4379       case MENU_ACTION_DOWN:
4380          *y += ozone_wiggle(ozone, t);
4381          break;
4382       case MENU_ACTION_UP:
4383          *y -= ozone_wiggle(ozone, t);
4384          break;
4385       default:
4386          break;
4387    }
4388 }
4389 
4390 menu_ctx_driver_t menu_ctx_ozone = {
4391    NULL,                         /* set_texture */
4392    ozone_messagebox,
4393    ozone_render,
4394    ozone_frame,
4395    ozone_init,
4396    ozone_free,
4397    ozone_context_reset,
4398    ozone_context_destroy,
4399    ozone_populate_entries,
4400    ozone_toggle,
4401    ozone_navigation_clear,
4402    NULL,
4403    NULL,
4404    ozone_navigation_set,
4405    ozone_navigation_pointer_changed,
4406    ozone_navigation_alphabet,
4407    ozone_navigation_alphabet,
4408    ozone_menu_init_list,
4409    ozone_list_insert,
4410    NULL,                         /* list_prepend */
4411    ozone_list_free,
4412    ozone_list_clear,
4413    ozone_list_cache,
4414    ozone_list_push,
4415    ozone_list_get_selection,
4416    ozone_list_get_size,
4417    ozone_list_get_entry,
4418    NULL,                         /* list_set_selection */
4419    ozone_list_bind_init,
4420    NULL,
4421    "ozone",
4422    ozone_environ_cb,
4423    NULL,
4424    ozone_update_thumbnail_image,
4425    ozone_refresh_thumbnail_image,
4426    ozone_set_thumbnail_system,
4427    ozone_get_thumbnail_system,
4428    ozone_set_thumbnail_content,
4429    gfx_display_osk_ptr_at_pos,
4430    NULL,                         /* update_savestate_thumbnail_path */
4431    NULL,                         /* update_savestate_thumbnail_image */
4432    NULL,                         /* pointer_down */
4433    ozone_pointer_up,
4434    ozone_menu_entry_action
4435 };
4436