From 5f7d14966c1ef667590d82f09e5bd96161ea8566 Mon Sep 17 00:00:00 2001
From: sonninnos <45124675+sonninnos@users.noreply.github.com>
Date: Wed, 24 Aug 2022 13:35:54 +0300
Subject: [PATCH] (Ozone+XMB) Fullscreen thumbnail browsing (#14342)

---
 menu/drivers/ozone.c | 431 +++++++++++++++++++++++++++----------------
 menu/drivers/xmb.c   | 207 ++++++++++++---------
 retroarch.c          |   5 +-
 3 files changed, 390 insertions(+), 253 deletions(-)

diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c
index 89164971cc..e711e9b7df 100644
--- a/menu/drivers/ozone.c
+++ b/menu/drivers/ozone.c
@@ -456,6 +456,7 @@ struct ozone_handle
    size_t categories_selection_ptr; /* active tab id  */
    size_t categories_active_idx_old;
    size_t playlist_index;
+   size_t playlist_size;
    size_t playlist_collection_offset;
 
    size_t selection; /* currently selected entry */
@@ -616,6 +617,7 @@ struct ozone_handle
    bool pending_hide_thumbnail_bar;
    bool no_thumbnail_available;
    bool fullscreen_thumbnails_available;
+   bool want_fullscreen_thumbnails;
    bool show_fullscreen_thumbnails;
    bool selection_core_is_viewer;
    bool selection_core_is_viewer_real;
@@ -3357,12 +3359,28 @@ console_iterate:
             video_width, video_height);
 }
 
+static bool ozone_is_running_quick_menu(void)
+{
+   menu_entry_t entry;
+
+   MENU_ENTRY_INIT(entry);
+   entry.path_enabled     = false;
+   entry.value_enabled    = false;
+   entry.sublabel_enabled = false;
+   menu_entry_get(&entry, 0, 0, NULL, true);
+
+   return string_is_equal(entry.label, "resume_content") ||
+          string_is_equal(entry.label, "state_slot");
+}
+
 static void ozone_thumbnail_bar_hide_end(void *userdata)
 {
    ozone_handle_t *ozone             = (ozone_handle_t*) userdata;
    ozone->show_thumbnail_bar         = false;
    ozone->pending_hide_thumbnail_bar = false;
-   ozone->need_compute               = true;
+
+   if (!(ozone->is_quick_menu && ozone_is_running_quick_menu()))
+      ozone->need_compute               = true;
 }
 
 static bool ozone_is_load_content_playlist(void *userdata)
@@ -3382,18 +3400,60 @@ static bool ozone_is_load_content_playlist(void *userdata)
    return entry.type == FILE_TYPE_RPL_ENTRY;
 }
 
-static bool ozone_is_running_quick_menu(void)
+static void ozone_update_fullscreen_thumbnail_label(ozone_handle_t *ozone)
 {
-   menu_entry_t entry;
+   menu_entry_t selected_entry;
+   const char *thumbnail_label     = NULL;
 
-   MENU_ENTRY_INIT(entry);
-   entry.path_enabled     = false;
-   entry.value_enabled    = false;
-   entry.sublabel_enabled = false;
-   menu_entry_get(&entry, 0, 0, NULL, true);
+   char tmpstr[64];
+   tmpstr[0]                       = '\0';
 
-   return string_is_equal(entry.label, "resume_content") ||
-          string_is_equal(entry.label, "state_slot");
+   /* > Get menu entry */
+   MENU_ENTRY_INIT(selected_entry);
+   selected_entry.path_enabled     = false;
+   selected_entry.value_enabled    = false;
+   selected_entry.sublabel_enabled = false;
+   menu_entry_get(&selected_entry, 0, (size_t)ozone->selection, NULL, true);
+
+   /* > Get entry label */
+   if (!string_is_empty(selected_entry.rich_label))
+      thumbnail_label = selected_entry.rich_label;
+   /* > State slot label */
+   else if (ozone->is_quick_menu && (
+            string_is_equal(selected_entry.label, "state_slot") ||
+            string_is_equal(selected_entry.label, "loadstate") ||
+            string_is_equal(selected_entry.label, "savestate")
+         ))
+   {
+      snprintf(tmpstr, sizeof(tmpstr), "%s %d",
+            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STATE_SLOT),
+            config_get_ptr()->ints.state_slot);
+      thumbnail_label = tmpstr;
+   }
+   else if (string_to_unsigned(selected_entry.label) == MENU_ENUM_LABEL_STATE_SLOT)
+   {
+      snprintf(tmpstr, sizeof(tmpstr), "%s %d",
+            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STATE_SLOT),
+            string_to_unsigned(selected_entry.path));
+      thumbnail_label = tmpstr;
+   }
+   /* > Quick Menu playlist label */
+   else if (ozone->is_quick_menu)
+   {
+      const struct playlist_entry *entry = NULL;
+      playlist_get_index(playlist_get_cached(), ozone->playlist_index, &entry);
+      if (entry)
+         thumbnail_label = entry->label;
+   }
+   else
+      thumbnail_label = selected_entry.path;
+
+   /* > Sanity check */
+   if (!string_is_empty(thumbnail_label))
+      strlcpy(
+            ozone->fullscreen_thumbnail_label,
+            thumbnail_label,
+            sizeof(ozone->fullscreen_thumbnail_label));
 }
 
 static void ozone_update_savestate_thumbnail_path(void *data, unsigned i)
@@ -3473,6 +3533,7 @@ static void ozone_update_savestate_thumbnail_path(void *data, unsigned i)
 
                ozone->want_thumbnail_bar              = true;
                ozone->fullscreen_thumbnails_available = true;
+               ozone_update_fullscreen_thumbnail_label(ozone);
             }
             else if (!ozone->is_state_slot)
             {
@@ -3488,7 +3549,7 @@ static void ozone_update_savestate_thumbnail_path(void *data, unsigned i)
             ozone->fullscreen_thumbnails_available = false;
          }
 
-         if (ozone->show_thumbnail_bar != ozone->want_thumbnail_bar)
+         if (ozone->show_thumbnail_bar != ozone->want_thumbnail_bar && !ozone->pending_hide_thumbnail_bar)
             ozone->need_compute = true;
       }
    }
@@ -3803,7 +3864,7 @@ static void ozone_update_content_metadata(ozone_handle_t *ozone)
 
    ozone->selection_core_is_viewer_real  = ozone->selection_core_is_viewer;
 
-   if ((ozone->is_playlist && playlist) ||
+   if ((playlist && (ozone->is_playlist || (ozone->is_quick_menu && !ozone_is_running_quick_menu()))) ||
          (ozone->is_db_manager_list && ozone->depth == 4))
    {
       const char *core_label             = NULL;
@@ -3812,23 +3873,36 @@ static void ozone_update_content_metadata(ozone_handle_t *ozone)
       file_list_t *list                  = menu_entries_get_selection_buf_ptr(0);
       size_t playlist_index              = selection;
 
+      if (ozone->is_quick_menu)
+      {
+         bool content_runtime_log          = settings->bools.content_runtime_log;
+         bool content_runtime_log_aggr     = settings->bools.content_runtime_log_aggregate;
+         playlist_index                    = ozone->playlist_index;
+         list_size                         = ozone->playlist_size;
+
+         /* Fill play time if applicable */
+         if (content_runtime_log || content_runtime_log_aggr)
+            playlist_get_index(playlist, playlist_index, &entry);
+      }
       /* Get playlist index corresponding
        * to the selected entry */
-      if (list &&
+      else if (list &&
           (selection < list_size) &&
           (list->list[selection].type == FILE_TYPE_RPL_ENTRY))
       {
          bool content_runtime_log          = settings->bools.content_runtime_log;
          bool content_runtime_log_aggr     = settings->bools.content_runtime_log_aggregate;
          playlist_index                    = list->list[selection].entry_idx;
+
+         /* Remember playlist index for Quick Menu fullscreen thumbnail title */
+         ozone->playlist_index = playlist_index;
+         ozone->playlist_size  = list_size;
+
          /* Fill play time if applicable */
          if (content_runtime_log || content_runtime_log_aggr)
             playlist_get_index(playlist, playlist_index, &entry);
       }
 
-      /* Remember playlist index for Quick Menu fullscreen thumbnail title */
-      ozone->playlist_index = playlist_index;
-
       /* Fill entry enumeration */
       if (show_entry_idx)
       {
@@ -3843,10 +3917,20 @@ static void ozone_update_content_metadata(ozone_handle_t *ozone)
          ozone->selection_entry_enumeration[0] = '\0';
 
       /* Fill core name */
-      if (!core_name || string_is_equal(core_name, "DETECT"))
-         core_label = msg_hash_to_str(MSG_AUTODETECT);
+      if (entry)
+      {
+         if (!entry->core_name || string_is_equal(entry->core_name, "DETECT"))
+            core_label = msg_hash_to_str(MSG_AUTODETECT);
+         else
+            core_label = entry->core_name;
+      }
       else
-         core_label = core_name;
+      {
+         if (!core_name || string_is_equal(core_name, "DETECT"))
+            core_label = msg_hash_to_str(MSG_AUTODETECT);
+         else
+            core_label = core_name;
+      }
 
       snprintf(ozone->selection_core_name, sizeof(ozone->selection_core_name),
          "%s %s", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_CORE), core_label);
@@ -3874,7 +3958,7 @@ static void ozone_update_content_metadata(ozone_handle_t *ozone)
 
       if (entry)
       {
-         if (entry->runtime_status == PLAYLIST_RUNTIME_UNKNOWN)
+         if (entry->runtime_status == PLAYLIST_RUNTIME_UNKNOWN || ozone->is_quick_menu)
             runtime_update_playlist(
                   playlist, playlist_index,
                   directory_runtime_log,
@@ -4913,7 +4997,13 @@ static void ozone_compute_entries_position(
    float scale_factor            = ozone->last_scale_factor;
 
    if (ozone->show_thumbnail_bar != ozone->want_thumbnail_bar)
-      ozone_entries_update_thumbnail_bar(ozone, false, true);
+   {
+      if (!(ozone->pending_hide_thumbnail_bar && ozone->is_quick_menu))
+         ozone_entries_update_thumbnail_bar(ozone, false, true);
+   }
+
+   if (ozone->show_thumbnail_bar)
+      ozone_update_content_metadata(ozone);
 
    menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &i);
 
@@ -6513,9 +6603,6 @@ static void ozone_show_fullscreen_thumbnails(ozone_handle_t *ozone)
    uintptr_t alpha_tag                = (uintptr_t)&ozone->animations.fullscreen_thumbnail_alpha;
    uintptr_t scroll_tag               = (uintptr_t)selection_buf;
 
-   char tmpstr[64];
-   tmpstr[0] = '\0';
-
    /* Before showing fullscreen thumbnails, must
     * ensure that any existing fullscreen thumbnail
     * view is disabled... */
@@ -6538,10 +6625,6 @@ static void ozone_show_fullscreen_thumbnails(ozone_handle_t *ozone)
        * since only the right thumbnail is ever loaded */
       if (!gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT))
          return;
-
-      if (ozone->thumbnails.right.status     != GFX_THUMBNAIL_STATUS_AVAILABLE &&
-          ozone->thumbnails.savestate.status != GFX_THUMBNAIL_STATUS_AVAILABLE)
-         return;
    }
    else
    {
@@ -6575,53 +6658,7 @@ static void ozone_show_fullscreen_thumbnails(ozone_handle_t *ozone)
     * (used as title when fullscreen thumbnails
     * are shown) */
    ozone->fullscreen_thumbnail_label[0] = '\0';
-
-   /* > Get menu entry */
-   MENU_ENTRY_INIT(selected_entry);
-   selected_entry.path_enabled     = false;
-   selected_entry.value_enabled    = false;
-   selected_entry.sublabel_enabled = false;
-   menu_entry_get(&selected_entry, 0, (size_t)ozone->selection, NULL, true);
-
-   /* > Get entry label */
-   if (!string_is_empty(selected_entry.rich_label))
-      thumbnail_label = selected_entry.rich_label;
-   /* > State slot label */
-   else if (ozone->is_quick_menu && (
-            string_is_equal(selected_entry.label, "state_slot") ||
-            string_is_equal(selected_entry.label, "loadstate") ||
-            string_is_equal(selected_entry.label, "savestate")
-         ))
-   {
-      snprintf(tmpstr, sizeof(tmpstr), "%s %d",
-            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STATE_SLOT),
-            config_get_ptr()->ints.state_slot);
-      thumbnail_label = tmpstr;
-   }
-   else if (string_to_unsigned(selected_entry.label) == MENU_ENUM_LABEL_STATE_SLOT)
-   {
-      snprintf(tmpstr, sizeof(tmpstr), "%s %d",
-            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STATE_SLOT),
-            string_to_unsigned(selected_entry.path));
-      thumbnail_label = tmpstr;
-   }
-   /* > Quick Menu playlist label */
-   else if (ozone->is_quick_menu)
-   {
-      const struct playlist_entry *entry = NULL;
-      playlist_get_index(playlist_get_cached(), ozone->playlist_index, &entry);
-      if (entry)
-         thumbnail_label = entry->label;
-   }
-   else
-      thumbnail_label = selected_entry.path;
-
-   /* > Sanity check */
-   if (!string_is_empty(thumbnail_label))
-      strlcpy(
-            ozone->fullscreen_thumbnail_label,
-            thumbnail_label,
-            sizeof(ozone->fullscreen_thumbnail_label));
+   ozone_update_fullscreen_thumbnail_label(ozone);
 
    /* Configure fade in animation */
    animation_entry.easing_enum  = EASING_OUT_QUAD;
@@ -6650,7 +6687,7 @@ static void ozone_draw_fullscreen_thumbnails(
       unsigned video_height)
 {
    /* Check whether fullscreen thumbnails are visible */
-   if (ozone->animations.fullscreen_thumbnail_alpha > 0.0f)
+   if (ozone->animations.fullscreen_thumbnail_alpha > 0.0f || ozone->want_fullscreen_thumbnails)
    {
       /* Note: right thumbnail is drawn at the top
        * in the sidebar, so it becomes the *left*
@@ -6731,12 +6768,36 @@ static void ozone_draw_fullscreen_thumbnails(
       if (show_left_thumbnail)
          num_thumbnails++;
 
+      /* Prevent screen flashing when browsing in fullscreen thumbnail mode */
+      if (ozone->is_playlist && num_thumbnails < 1 && ozone->want_fullscreen_thumbnails &&
+            (right_thumbnail->status != GFX_THUMBNAIL_STATUS_MISSING &&
+             left_thumbnail->status  != GFX_THUMBNAIL_STATUS_MISSING))
+      {
+         /* Darken background */
+         gfx_display_draw_quad(
+               p_disp,
+               userdata,
+               video_width,
+               video_height,
+               0,
+               ozone->dimensions.header_height + ozone->dimensions.spacer_1px,
+               width,
+               (unsigned)view_height,
+               width,
+               height,
+               background_color,
+               NULL);
+         return;
+      }
+
       /* Do nothing if both thumbnails are missing
        * > Note: Baring inexplicable internal errors, this
        *   can never happen...
        * > Return instead of error to keep fullscreen
        *   mode after menu/fullscreen toggle */
-      if (num_thumbnails < 1)
+      if (num_thumbnails < 1 &&
+            (right_thumbnail->status == GFX_THUMBNAIL_STATUS_MISSING &&
+             left_thumbnail->status  == GFX_THUMBNAIL_STATUS_MISSING))
          return;
 
       /* Get base thumbnail dimensions + draw positions */
@@ -7323,41 +7384,6 @@ static enum menu_action ozone_parse_menu_entry_action(
       gfx_thumbnail_set_stream_delay(ozone->thumbnails.stream_delay);
    }
 
-   /* If fullscreen thumbnail view is active, any
-    * valid menu action will disable it... */
-   if (ozone->show_fullscreen_thumbnails)
-   {
-      if (action != MENU_ACTION_NOOP && action != MENU_ACTION_TOGGLE)
-      {
-         ozone_hide_fullscreen_thumbnails(ozone, true);
-
-         /* ...and any action other than Select/OK
-          * is ignored
-          * > We allow pass-through of Select/OK since
-          *   users may want to run content directly
-          *   after viewing fullscreen thumbnails,
-          *   and having to press RetroPad A or the Return
-          *   key twice is navigationally confusing
-          * > Note that we can only do this for non-pointer
-          *   input
-          * > Note that we don't do this when viewing a
-          *   file list, since there is no quick menu
-          *   in this case - i.e. content loads directly,
-          *   and a sudden transition from fullscreen
-          *   thumbnail to content is jarring...
-          * > We also don't do this when viewing a database
-          *   manager list, because the menu transition
-          *   detection becomes too cumbersome... */
-         if (ozone->is_file_list ||
-             ozone->is_db_manager_list ||
-             ozone->is_quick_menu ||
-             ozone->is_state_slot ||
-             ((action != MENU_ACTION_SELECT) &&
-              (action != MENU_ACTION_OK)))
-            return MENU_ACTION_NOOP;
-      }
-   }
-
    horizontal_list_size       = (unsigned)ozone->horizontal_list.size;
 
    ozone->messagebox_state    = menu_input_dialog_get_display_kb();
@@ -7384,9 +7410,17 @@ static enum menu_action ozone_parse_menu_entry_action(
           * is not in the sidebar, attempt to show
           * fullscreen thumbnail view */
          if (ozone->fullscreen_thumbnails_available &&
-             !ozone->cursor_in_sidebar)
+               !ozone->show_fullscreen_thumbnails &&
+               !ozone->cursor_in_sidebar)
          {
             ozone_show_fullscreen_thumbnails(ozone);
+            ozone->want_fullscreen_thumbnails = true;
+            new_action = MENU_ACTION_NOOP;
+         }
+         else if (ozone->show_fullscreen_thumbnails || ozone->want_fullscreen_thumbnails)
+         {
+            ozone_hide_fullscreen_thumbnails(ozone, true);
+            ozone->want_fullscreen_thumbnails = false;
             new_action = MENU_ACTION_NOOP;
          }
          break;
@@ -7394,10 +7428,18 @@ static enum menu_action ozone_parse_menu_entry_action(
          ozone->cursor_mode = false;
 
          if (ozone->fullscreen_thumbnails_available &&
+               !ozone->show_fullscreen_thumbnails &&
                (ozone->is_state_slot ||
                   (ozone->is_quick_menu && !string_is_empty(ozone->savestate_thumbnail_file_path))))
          {
             ozone_show_fullscreen_thumbnails(ozone);
+            ozone->want_fullscreen_thumbnails = true;
+            new_action = MENU_ACTION_NOOP;
+         }
+         else if (ozone->show_fullscreen_thumbnails && !ozone->is_playlist)
+         {
+            ozone_hide_fullscreen_thumbnails(ozone, true);
+            ozone->want_fullscreen_thumbnails = false;
             new_action = MENU_ACTION_NOOP;
          }
          break;
@@ -7425,6 +7467,9 @@ static enum menu_action ozone_parse_menu_entry_action(
          else if (!menu_navigation_wraparound_enable && selection == selection_total - 1)
             ozone_start_cursor_wiggle(ozone, MENU_ACTION_DOWN);
 
+         if (ozone->show_fullscreen_thumbnails && ozone->is_quick_menu)
+            return MENU_ACTION_NOOP;
+
          /* If pointer is active and current selection
           * is off screen, auto select *centre* item */
          if (ozone->cursor_mode)
@@ -7453,6 +7498,9 @@ static enum menu_action ozone_parse_menu_entry_action(
          else if (!menu_navigation_wraparound_enable && selection == 0)
             ozone_start_cursor_wiggle(ozone, MENU_ACTION_UP);
 
+         if (ozone->show_fullscreen_thumbnails && ozone->is_quick_menu)
+            return MENU_ACTION_NOOP;
+
          /* If pointer is active and current selection
           * is off screen, auto select *centre* item */
          if (ozone->cursor_mode)
@@ -7468,7 +7516,6 @@ static enum menu_action ozone_parse_menu_entry_action(
          {
             new_action      = MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE;
             ozone_start_cursor_wiggle(ozone, MENU_ACTION_LEFT);
-
             break;
          }
          else if (ozone->depth > 1)
@@ -7479,8 +7526,16 @@ static enum menu_action ozone_parse_menu_entry_action(
             if (!menu_navigation_wraparound_enable && selection == 0 && !is_current_entry_settings)
                ozone_start_cursor_wiggle(ozone, MENU_ACTION_DOWN);
 
+            if (ozone->show_fullscreen_thumbnails && (ozone->is_quick_menu && !ozone_is_running_quick_menu()))
+               return MENU_ACTION_NOOP;
+
             break;
          }
+         else if (ozone->depth == 1 && ozone->show_fullscreen_thumbnails && ozone->is_playlist)
+         {
+            ozone_hide_fullscreen_thumbnails(ozone, true);
+            ozone->want_fullscreen_thumbnails = false;
+         }
 
          ozone_go_to_sidebar(ozone, ozone_collapse_sidebar, tag);
 
@@ -7501,6 +7556,10 @@ static enum menu_action ozone_parse_menu_entry_action(
             else if (!menu_navigation_wraparound_enable && selection == selection_total - 1 && !is_current_entry_settings)
                ozone_start_cursor_wiggle(ozone, MENU_ACTION_DOWN);
 
+            if (ozone->show_fullscreen_thumbnails &&
+                  (ozone->is_playlist || (ozone->is_quick_menu && !ozone_is_running_quick_menu())))
+               return MENU_ACTION_NOOP;
+
             break;
          }
 
@@ -7521,12 +7580,23 @@ static enum menu_action ozone_parse_menu_entry_action(
                ozone->show_thumbnail_bar && ozone->fullscreen_thumbnails_available)
          {
             ozone_show_fullscreen_thumbnails(ozone);
+            ozone->want_fullscreen_thumbnails = true;
             new_action = MENU_ACTION_NOOP;
             break;
          }
 
+         if (ozone->show_fullscreen_thumbnails || ozone->want_fullscreen_thumbnails)
+         {
+            ozone_hide_fullscreen_thumbnails(ozone, true);
+            ozone->want_fullscreen_thumbnails = false;
+            if (!ozone->is_state_slot && !ozone->is_playlist)
+               new_action = MENU_ACTION_NOOP;
+            break;
+         }
+
          if (ozone->cursor_in_sidebar)
          {
+            ozone_refresh_sidebars(ozone, ozone_collapse_sidebar, ozone->last_height);
             if (!ozone->empty_playlist)
                ozone_leave_sidebar(ozone, ozone_collapse_sidebar, tag);
             new_action = MENU_ACTION_ACCESSIBILITY_SPEAK_LABEL;
@@ -7555,6 +7625,14 @@ static enum menu_action ozone_parse_menu_entry_action(
             break;
          }
 
+         if (ozone->show_fullscreen_thumbnails || ozone->want_fullscreen_thumbnails)
+         {
+            ozone_hide_fullscreen_thumbnails(ozone, true);
+            ozone->want_fullscreen_thumbnails = false;
+            new_action = MENU_ACTION_NOOP;
+            break;
+         }
+
          if (menu_entries_get_stack_size(0) == 1)
          {
             ozone_go_to_sidebar(ozone, ozone_collapse_sidebar, tag);
@@ -7572,6 +7650,9 @@ static enum menu_action ozone_parse_menu_entry_action(
             break;
          }
 
+         if (ozone->show_fullscreen_thumbnails && ozone->is_quick_menu)
+            return MENU_ACTION_NOOP;
+
          /* If pointer is active and current selection
           * is off screen, auto select *last* item */
          if (ozone->cursor_mode)
@@ -7590,6 +7671,9 @@ static enum menu_action ozone_parse_menu_entry_action(
             break;
          }
 
+         if (ozone->show_fullscreen_thumbnails && ozone->is_quick_menu)
+            return MENU_ACTION_NOOP;
+
          /* If pointer is active and current selection
           * is off screen, auto select *first* item */
          if (ozone->cursor_mode)
@@ -7991,6 +8075,10 @@ static void ozone_refresh_thumbnail_image(void *data, unsigned i)
    if (!ozone)
       return;
 
+   /* Refresh metadata */
+   if (!i)
+      ozone_update_content_metadata(ozone);
+
    /* Only refresh thumbnails if thumbnails are enabled
     * and we are currently viewing a playlist */
    if ((gfx_thumbnail_is_enabled(ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
@@ -9289,6 +9377,7 @@ static void ozone_draw_header(
    float scale_factor        = ozone->last_scale_factor;
    unsigned logo_icon_size   = 60 * scale_factor;
    unsigned status_icon_size = 92 * scale_factor;
+   unsigned status_row_size  = 160 * scale_factor;
    unsigned seperator_margin = 30 * scale_factor;
    enum gfx_animation_ticker_type
       menu_ticker_type       = (enum gfx_animation_ticker_type)settings->uints.menu_ticker_type;
@@ -9327,43 +9416,6 @@ static void ozone_draw_header(
          ozone->theme->header_footer_separator,
          NULL);
 
-   /* Title */
-   if (use_smooth_ticker)
-   {
-      ticker_smooth.font        = ozone->fonts.title.font;
-      ticker_smooth.selected    = true;
-      ticker_smooth.field_width = (video_width - (128 + 47 + 180) * scale_factor);
-      ticker_smooth.src_str     = ozone->show_fullscreen_thumbnails ? ozone->fullscreen_thumbnail_label : ozone->title;
-      ticker_smooth.dst_str     = title;
-      ticker_smooth.dst_str_len = sizeof(title);
-
-      gfx_animation_ticker_smooth(&ticker_smooth);
-   }
-   else
-   {
-      ticker.s        = title;
-      ticker.len      = (video_width - (128 + 47 + 180) * scale_factor) / ozone->fonts.title.glyph_width;
-      ticker.str      = ozone->show_fullscreen_thumbnails ? ozone->fullscreen_thumbnail_label : ozone->title;
-      ticker.selected = true;
-
-      gfx_animation_ticker(&ticker);
-   }
-
-   gfx_display_draw_text(
-         ozone->fonts.title.font,
-         title,
-         ticker_x_offset + 128 * scale_factor,
-           ozone->dimensions.header_height / 2 
-         + ozone->fonts.title.line_centre_offset,
-         video_width,
-         video_height,
-         ozone->theme->text_rgba,
-         TEXT_ALIGN_LEFT,
-         1.0f,
-         false,
-         1.0f,
-         false);
-
    /* Icon */
    if (dispctx)
    {
@@ -9427,12 +9479,13 @@ static void ozone_draw_header(
 
       if (powerstate.battery_enabled)
       {
-         timedate_offset = 95 * scale_factor;
+         timedate_offset = 90 * scale_factor;
+         status_row_size += timedate_offset;
 
          gfx_display_draw_text(
                ozone->fonts.time.font,
                msg,
-               video_width - 85 * scale_factor,
+               video_width - 55 * scale_factor,
                  ozone->dimensions.header_height / 2 
                + ozone->fonts.time.line_centre_offset,
                video_width,
@@ -9456,8 +9509,13 @@ static void ozone_draw_header(
                      video_height,
                      status_icon_size,
                      status_icon_size,
-                     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],
-                     video_width - (60 + 58) * scale_factor,
+                     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],
+                     video_width - (55 + 32) * scale_factor,
                      0,
                      video_width,
                      video_height,
@@ -9489,8 +9547,8 @@ static void ozone_draw_header(
       gfx_display_draw_text(
             ozone->fonts.time.font,
             timedate,
-            video_width - (85 * scale_factor) - timedate_offset,
-              ozone->dimensions.header_height / 2 
+            video_width - (64 * scale_factor) - timedate_offset,
+              ozone->dimensions.header_height / 2
             + ozone->fonts.time.line_centre_offset,
             video_width,
             video_height,
@@ -9514,7 +9572,7 @@ static void ozone_draw_header(
                   status_icon_size,
                   status_icon_size,
                   ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CLOCK],
-                  video_width - (60 + 54) * scale_factor - timedate_offset,
+                  video_width - (64 + 28) * scale_factor - timedate_offset,
                   0,
                   video_width,
                   video_height,
@@ -9525,7 +9583,46 @@ static void ozone_draw_header(
          if (dispctx->blend_end)
             dispctx->blend_end(userdata);
       }
+
+      status_row_size += 240 * scale_factor;
    }
+
+   /* Title */
+   if (use_smooth_ticker)
+   {
+      ticker_smooth.font        = ozone->fonts.title.font;
+      ticker_smooth.selected    = true;
+      ticker_smooth.field_width = video_width - status_row_size;
+      ticker_smooth.src_str     = ozone->show_fullscreen_thumbnails ? ozone->fullscreen_thumbnail_label : ozone->title;
+      ticker_smooth.dst_str     = title;
+      ticker_smooth.dst_str_len = sizeof(title);
+
+      gfx_animation_ticker_smooth(&ticker_smooth);
+   }
+   else
+   {
+      ticker.s        = title;
+      ticker.len      = video_width - status_row_size / ozone->fonts.title.glyph_width;
+      ticker.str      = ozone->show_fullscreen_thumbnails ? ozone->fullscreen_thumbnail_label : ozone->title;
+      ticker.selected = true;
+
+      gfx_animation_ticker(&ticker);
+   }
+
+   gfx_display_draw_text(
+         ozone->fonts.title.font,
+         title,
+         ticker_x_offset + 128 * scale_factor,
+           ozone->dimensions.header_height / 2
+         + ozone->fonts.title.line_centre_offset,
+         video_width,
+         video_height,
+         ozone->theme->text_rgba,
+         TEXT_ALIGN_LEFT,
+         1.0f,
+         false,
+         1.0f,
+         false);
 }
 
 static void ozone_draw_footer(
@@ -9542,7 +9639,7 @@ static void ozone_draw_footer(
    bool menu_core_enable                  = settings->bools.menu_core_enable;
    float scale_factor                     = ozone->last_scale_factor;
    unsigned seperator_margin              = 30 * scale_factor;
-   float footer_margin                    = 59 * scale_factor;
+   float footer_margin                    = 42 * scale_factor;
    float footer_text_y                    = (float)video_height -
          (ozone->dimensions.footer_height / 2.0f) +
          ozone->fonts.footer.line_centre_offset;
@@ -9957,9 +10054,9 @@ static void ozone_selection_changed(ozone_handle_t *ozone, bool allow_animation)
 
       /* Update thumbnail */
       if (gfx_thumbnail_is_enabled(
-               ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
+            ozone->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) ||
           gfx_thumbnail_is_enabled(
-             ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
+            ozone->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
       {
          bool update_thumbnails = false;
 
@@ -10116,6 +10213,18 @@ static void ozone_frame(void *data, video_frame_info_t *video_info)
       }
    }
 
+   if ((ozone->is_playlist || ozone->is_state_slot) &&
+         ozone->show_fullscreen_thumbnails && (size_t)ozone->selection != ozone->fullscreen_thumbnail_selection)
+   {
+      ozone->need_compute = true;
+      ozone_show_fullscreen_thumbnails(ozone);
+   }
+   else if (!ozone->show_fullscreen_thumbnails && ozone->want_fullscreen_thumbnails)
+   {
+      ozone->need_compute = true;
+      ozone_show_fullscreen_thumbnails(ozone);
+   }
+
    if (ozone->first_frame)
    {
       menu_input_get_pointer_state(&ozone->pointer);
diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c
index 10d4d43532..c3498a518e 100644
--- a/menu/drivers/xmb.c
+++ b/menu/drivers/xmb.c
@@ -412,6 +412,7 @@ typedef struct xmb_handle
 
    bool fullscreen_thumbnails_available;
    bool show_fullscreen_thumbnails;
+   bool want_fullscreen_thumbnails;
    bool skip_thumbnail_reset;
    bool show_mouse;
    bool show_screensaver;
@@ -1160,6 +1161,62 @@ static bool xmb_is_running_quick_menu(void)
           string_is_equal(entry.label, "state_slot");
 }
 
+static void xmb_update_fullscreen_thumbnail_label(xmb_handle_t *xmb)
+{
+   menu_entry_t selected_entry;
+   const char *thumbnail_label     = NULL;
+
+   char tmpstr[64];
+   tmpstr[0]                       = '\0';
+
+   /* > Get menu entry */
+   MENU_ENTRY_INIT(selected_entry);
+   selected_entry.path_enabled     = false;
+   selected_entry.value_enabled    = false;
+   selected_entry.sublabel_enabled = false;
+   menu_entry_get(&selected_entry, 0, menu_navigation_get_selection(), NULL, true);
+
+   /* > Get entry label */
+   if (!string_is_empty(selected_entry.rich_label))
+      thumbnail_label = selected_entry.rich_label;
+   /* > State slot label */
+   else if (xmb->is_quick_menu && (
+            string_is_equal(selected_entry.label, "state_slot") ||
+            string_is_equal(selected_entry.label, "loadstate") ||
+            string_is_equal(selected_entry.label, "savestate")
+         ))
+   {
+      snprintf(tmpstr, sizeof(tmpstr), "%s %d",
+            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STATE_SLOT),
+            config_get_ptr()->ints.state_slot);
+      thumbnail_label = tmpstr;
+   }
+   else if (string_to_unsigned(selected_entry.label) == MENU_ENUM_LABEL_STATE_SLOT)
+   {
+      snprintf(tmpstr, sizeof(tmpstr), "%s %d",
+            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STATE_SLOT),
+            string_to_unsigned(selected_entry.path));
+      thumbnail_label = tmpstr;
+   }
+   /* > Quick Menu playlist label */
+   else if (xmb->is_quick_menu)
+   {
+      const struct playlist_entry *entry = NULL;
+      playlist_get_index(playlist_get_cached(), xmb->playlist_index, &entry);
+      if (entry)
+         thumbnail_label = entry->label;
+   }
+   else
+      thumbnail_label = selected_entry.path;
+
+   /* > Sanity check */
+   if (!string_is_empty(thumbnail_label))
+      strlcpy(
+            xmb->fullscreen_thumbnail_label,
+            thumbnail_label,
+            sizeof(xmb->fullscreen_thumbnail_label));
+}
+
 static void xmb_update_savestate_thumbnail_path(void *data, unsigned i)
 {
    settings_t *settings = config_get_ptr();
@@ -1236,6 +1293,7 @@ static void xmb_update_savestate_thumbnail_path(void *data, unsigned i)
                      sizeof(xmb->savestate_thumbnail_file_path));
 
                xmb->fullscreen_thumbnails_available = true;
+               xmb_update_fullscreen_thumbnail_label(xmb);
             }
          }
       }
@@ -4053,9 +4111,6 @@ static void xmb_show_fullscreen_thumbnails(
    uintptr_t              alpha_tag   = (uintptr_t)
       &xmb->fullscreen_thumbnail_alpha;
 
-   char tmpstr[64];
-   tmpstr[0] = '\0';
-
    /* We can only enable fullscreen thumbnails if
     * current selection has at least one valid thumbnail
     * and all thumbnails for current selection are already
@@ -4114,53 +4169,7 @@ static void xmb_show_fullscreen_thumbnails(
     * (used as title when fullscreen thumbnails
     * are shown) */
    xmb->fullscreen_thumbnail_label[0] = '\0';
-
-   /* > Get menu entry */
-   MENU_ENTRY_INIT(selected_entry);
-   selected_entry.path_enabled     = false;
-   selected_entry.value_enabled    = false;
-   selected_entry.sublabel_enabled = false;
-   menu_entry_get(&selected_entry, 0, selection, NULL, true);
-
-   /* > Get entry label */
-   if (!string_is_empty(selected_entry.rich_label))
-      thumbnail_label = selected_entry.rich_label;
-   /* > State slot label */
-   else if (xmb->is_quick_menu && (
-            string_is_equal(selected_entry.label, "state_slot") ||
-            string_is_equal(selected_entry.label, "loadstate") ||
-            string_is_equal(selected_entry.label, "savestate")
-         ))
-   {
-      snprintf(tmpstr, sizeof(tmpstr), "%s %d",
-            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STATE_SLOT),
-            config_get_ptr()->ints.state_slot);
-      thumbnail_label = tmpstr;
-   }
-   else if (string_to_unsigned(selected_entry.label) == MENU_ENUM_LABEL_STATE_SLOT)
-   {
-      snprintf(tmpstr, sizeof(tmpstr), "%s %d",
-            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STATE_SLOT),
-            string_to_unsigned(selected_entry.path));
-      thumbnail_label = tmpstr;
-   }
-   /* > Quick Menu playlist label */
-   else if (xmb->is_quick_menu)
-   {
-      const struct playlist_entry *entry = NULL;
-      playlist_get_index(playlist_get_cached(), xmb->playlist_index, &entry);
-      if (entry)
-         thumbnail_label = entry->label;
-   }
-   else
-      thumbnail_label = selected_entry.path;
-
-   /* > Sanity check */
-   if (!string_is_empty(thumbnail_label))
-      strlcpy(
-            xmb->fullscreen_thumbnail_label,
-            thumbnail_label,
-            sizeof(xmb->fullscreen_thumbnail_label));
+   xmb_update_fullscreen_thumbnail_label(xmb);
 
    /* Configure fade in animation */
    animation_entry.easing_enum  = EASING_OUT_QUAD;
@@ -4184,42 +4193,14 @@ static enum menu_action xmb_parse_menu_entry_action(
 {
    enum menu_action new_action = action;
 
-   /* If fullscreen thumbnail view is active, any
-    * valid menu action will disable it... */
-   if (xmb->show_fullscreen_thumbnails)
-   {
-      if (action != MENU_ACTION_NOOP && action != MENU_ACTION_TOGGLE)
-      {
-         xmb_hide_fullscreen_thumbnails(xmb, true);
-
-         /* ...and any action other than Select/OK
-          * is ignored
-          * > We allow pass-through of Select/OK since
-          *   users may want to run content directly
-          *   after viewing fullscreen thumbnails,
-          *   and having to press RetroPad A or the Return
-          *   key twice is navigationally confusing
-          * > Note that we can only do this for non-pointer
-          *   input
-          * > Note that we don't do this when viewing a
-          *   file list, since there is no quick menu
-          *   in this case - i.e. content loads directly,
-          *   and a sudden transition from fullscreen
-          *   thumbnail to content is jarring... */
-         if (xmb->is_file_list ||
-             xmb->is_quick_menu ||
-             xmb->is_state_slot ||
-               ((action != MENU_ACTION_SELECT) &&
-                (action != MENU_ACTION_OK)))
-            return MENU_ACTION_NOOP;
-      }
-   }
-
    /* Scan user inputs */
    switch (action)
    {
       case MENU_ACTION_LEFT:
       case MENU_ACTION_RIGHT:
+         if (xmb->show_fullscreen_thumbnails && (xmb->is_quick_menu && !xmb_is_running_quick_menu()))
+            return MENU_ACTION_NOOP;
+
          /* Check whether left/right action will
           * trigger a tab switch event */
          if (xmb->depth == 1)
@@ -4256,26 +4237,35 @@ static enum menu_action xmb_parse_menu_entry_action(
 
          /* If this is a menu with thumbnails, attempt
           * to show fullscreen thumbnail view */
-         if (xmb->fullscreen_thumbnails_available)
+         if (xmb->fullscreen_thumbnails_available &&
+               !xmb->show_fullscreen_thumbnails)
          {
-            size_t selection = menu_navigation_get_selection();
-
-            /* Before showing fullscreen thumbnails, must
-             * ensure that any existing fullscreen thumbnail
-             * view is disabled... */
             xmb_hide_fullscreen_thumbnails(xmb, false);
-            if (xmb->fullscreen_thumbnails_available)
-               xmb_show_fullscreen_thumbnails(xmb, selection);
+            xmb_show_fullscreen_thumbnails(xmb, menu_navigation_get_selection());
+            xmb->want_fullscreen_thumbnails = true;
             new_action = MENU_ACTION_NOOP;
          }
+         else if (xmb->show_fullscreen_thumbnails || xmb->want_fullscreen_thumbnails)
+         {
+            xmb_hide_fullscreen_thumbnails(xmb, true);
+            xmb->want_fullscreen_thumbnails = false;
+         }
          break;
       case MENU_ACTION_SCAN:
          if (xmb->fullscreen_thumbnails_available &&
+               !xmb->show_fullscreen_thumbnails &&
                ((xmb->is_state_slot) ||
                 (xmb->is_quick_menu && !string_is_empty(xmb->savestate_thumbnail_file_path))))
          {
             xmb_hide_fullscreen_thumbnails(xmb, false);
             xmb_show_fullscreen_thumbnails(xmb, menu_navigation_get_selection());
+            xmb->want_fullscreen_thumbnails = true;
+            new_action = MENU_ACTION_NOOP;
+         }
+         else if (xmb->show_fullscreen_thumbnails && !xmb->is_playlist)
+         {
+            xmb_hide_fullscreen_thumbnails(xmb, true);
+            xmb->want_fullscreen_thumbnails = false;
             new_action = MENU_ACTION_NOOP;
          }
          break;
@@ -4292,10 +4282,32 @@ static enum menu_action xmb_parse_menu_entry_action(
             xmb_show_fullscreen_thumbnails(xmb, menu_navigation_get_selection());
             new_action = MENU_ACTION_NOOP;
          }
+
+         if (xmb->show_fullscreen_thumbnails || xmb->want_fullscreen_thumbnails)
+         {
+            xmb_hide_fullscreen_thumbnails(xmb, true);
+            xmb->want_fullscreen_thumbnails = false;
+            if (!xmb->is_state_slot && !xmb->is_playlist)
+               return MENU_ACTION_NOOP;
+         }
          break;
       case MENU_ACTION_CANCEL:
          if (xmb->is_state_slot)
             xmb->skip_thumbnail_reset = true;
+
+         if (xmb->show_fullscreen_thumbnails || xmb->want_fullscreen_thumbnails)
+         {
+            xmb_hide_fullscreen_thumbnails(xmb, true);
+            xmb->want_fullscreen_thumbnails = false;
+            return MENU_ACTION_NOOP;
+         }
+         break;
+      case MENU_ACTION_UP:
+      case MENU_ACTION_DOWN:
+      case MENU_ACTION_SCROLL_UP:
+      case MENU_ACTION_SCROLL_DOWN:
+         if (xmb->show_fullscreen_thumbnails && xmb->is_quick_menu)
+            return MENU_ACTION_NOOP;
          break;
       default:
          /* In all other cases, pass through input
@@ -4792,7 +4804,7 @@ static void xmb_draw_fullscreen_thumbnails(
       settings_t *settings, size_t selection)
 {
    /* Check whether fullscreen thumbnails are visible */
-   if (xmb->fullscreen_thumbnail_alpha > 0.0f)
+   if (xmb->fullscreen_thumbnail_alpha > 0.0f || xmb->want_fullscreen_thumbnails)
    {
       int header_margin;
       int thumbnail_box_width;
@@ -4902,7 +4914,9 @@ static void xmb_draw_fullscreen_thumbnails(
        *   can never happen...
        * > Return instead of error to keep fullscreen
        *   mode after menu/fullscreen toggle */
-      if (num_thumbnails < 1)
+      if (num_thumbnails < 1 &&
+            (right_thumbnail->status == GFX_THUMBNAIL_STATUS_MISSING &&
+             left_thumbnail->status  == GFX_THUMBNAIL_STATUS_MISSING))
          return;
 
       /* Get base thumbnail dimensions + draw positions */
@@ -5387,6 +5401,17 @@ static void xmb_frame(void *data, video_frame_info_t *video_info)
          xmb->fullscreen_thumbnails_available = true;
    }
 
+   /* Allow browsing playlist in fullscreen thumbnail mode */
+   if ((xmb->is_playlist || xmb->is_state_slot) &&
+         xmb->show_fullscreen_thumbnails &&
+         xmb->fullscreen_thumbnails_available &&
+         menu_navigation_get_selection() != xmb->fullscreen_thumbnail_selection)
+      xmb_show_fullscreen_thumbnails(xmb, menu_navigation_get_selection());
+   else if (!xmb->show_fullscreen_thumbnails &&
+         xmb->fullscreen_thumbnails_available &&
+         xmb->want_fullscreen_thumbnails)
+      xmb_show_fullscreen_thumbnails(xmb, menu_navigation_get_selection());
+
    /* Note: This is incredibly ugly, but there are
     * so many combinations here that we would go insane
     * trying to rationalise this any further... */
diff --git a/retroarch.c b/retroarch.c
index 19977d97d5..3964d8b03d 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -2571,6 +2571,7 @@ bool command_event(enum event_command cmd, void *data)
             const char *core_path          = "DETECT";
             size_t *playlist_index         = (size_t*)data;
             struct playlist_entry entry    = {0};
+            unsigned i                     = 0;
 
             /* the update function reads our entry as const,
              * so these casts are safe */
@@ -2580,9 +2581,11 @@ bool command_event(enum event_command cmd, void *data)
             command_playlist_update_write(
                   NULL, *playlist_index, &entry);
 
+            /* Update playlist metadata */
+            menu_driver_ctl(RARCH_MENU_CTL_REFRESH_THUMBNAIL_IMAGE, &i);
+
             runloop_msg_queue_push(msg_hash_to_str(MSG_RESET_CORE_ASSOCIATION), 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
             break;
-
          }
       case CMD_EVENT_RESTART_RETROARCH:
          if (!frontend_driver_set_fork(FRONTEND_FORK_RESTART))