/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2014-2017 - Jean-André Santoni
 *  Copyright (C) 2015-2018 - Andre Leiradella
 *  Copyright (C) 2018-2020 - natinusala
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include <retro_miscellaneous.h>
#include <retro_inline.h>

#include <queues/fifo_queue.h>
#include <file/file_path.h>
#include <streams/file_stream.h>
#include <string/stdstring.h>
#include <retro_math.h>

#include "gfx_display.h"
#include "gfx_widgets.h"
#include "font_driver.h"

#include "../msg_hash.h"

#include "../tasks/task_content.h"

#ifdef HAVE_THREADS
#define SLOCK_LOCK(x) slock_lock(x)
#define SLOCK_UNLOCK(x) slock_unlock(x)
#else
#define SLOCK_LOCK(x)
#define SLOCK_UNLOCK(x)
#endif

#define BASE_FONT_SIZE 32.0f

#define MSG_QUEUE_FONT_SIZE (BASE_FONT_SIZE * 0.69f)

#ifdef HAVE_MENU
#define ANIMATION_LOAD_CONTENT_DURATION            333

#define LOAD_CONTENT_ANIMATION_INITIAL_ICON_SIZE   320
#define LOAD_CONTENT_ANIMATION_TARGET_ICON_SIZE    240
#endif

/* TODO/FIXME - global state - perhaps move outside this file */
static float msg_queue_background[16]                    =
COLOR_HEX_TO_FLOAT(0x3A3A3A, 1.0f);
static float msg_queue_info[16]                          =
COLOR_HEX_TO_FLOAT(0x12ACF8, 1.0f);

/* Color of first progress bar in a task message */
static float msg_queue_task_progress_1[16]               = 
COLOR_HEX_TO_FLOAT(0x397869, 1.0f);

/* Color of second progress bar in a task message 
 * (for multiple tasks with same message) */
static float msg_queue_task_progress_2[16]               = 
COLOR_HEX_TO_FLOAT(0x317198, 1.0f);

#if 0
static float color_task_progress_bar[16]                 = 
COLOR_HEX_TO_FLOAT(0x22B14C, 1.0f);
#endif
static float gfx_widgets_pure_white[16]                  = {
      1.00, 1.00, 1.00, 1.00,
      1.00, 1.00, 1.00, 1.00,
      1.00, 1.00, 1.00, 1.00,
      1.00, 1.00, 1.00, 1.00,
};

static float gfx_widgets_backdrop_orig[16]               = {
   0.00, 0.00, 0.00, 0.75,
   0.00, 0.00, 0.00, 0.75,
   0.00, 0.00, 0.00, 0.75,
   0.00, 0.00, 0.00, 0.75,
};

static float gfx_widgets_backdrop[16]                    = {
      0.00, 0.00, 0.00, 0.75,
      0.00, 0.00, 0.00, 0.75,
      0.00, 0.00, 0.00, 0.75,
      0.00, 0.00, 0.00, 0.75,
};

/* Icons */
static const char 
*gfx_widgets_icons_names[MENU_WIDGETS_ICON_LAST]         = {
   "menu_pause.png",
   "menu_frameskip.png",
   "menu_rewind.png",
   "resume.png",

   "menu_hourglass.png",
   "menu_check.png",

   "menu_info.png",

   "menu_achievements.png"
};

/* Forward declarations */
#ifdef HAVE_MENU
bool menu_driver_get_load_content_animation_data(
      uintptr_t *icon, char **playlist_name);
#endif
static void gfx_widgets_context_reset(
      dispgfx_widget_t *p_dispwidget,
      bool is_threaded,
      unsigned width, unsigned height, bool fullscreen,
      const char *dir_assets, char *font_path);
static void gfx_widgets_context_destroy(dispgfx_widget_t *p_dispwidget);
static void gfx_widgets_free(dispgfx_widget_t *p_dispwidget);
static void gfx_widgets_layout(dispgfx_widget_t *p_dispwidget,
      bool is_threaded, const char *dir_assets, char *font_path);

gfx_widget_font_data_t* gfx_widgets_get_font_regular(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return &p_dispwidget->gfx_widget_fonts.regular;
}

gfx_widget_font_data_t* gfx_widgets_get_font_bold(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return &p_dispwidget->gfx_widget_fonts.bold;
}

gfx_widget_font_data_t* gfx_widgets_get_font_msg_queue(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return &p_dispwidget->gfx_widget_fonts.msg_queue;
}

float* gfx_widgets_get_pure_white(void)
{
   return gfx_widgets_pure_white;
}

float* gfx_widgets_get_backdrop_orig(void)
{
   return gfx_widgets_backdrop_orig;
}

/* Messages queue */

uintptr_t gfx_widgets_get_generic_tag(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return p_dispwidget->gfx_widgets_generic_tag;
}

unsigned gfx_widgets_get_padding(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return p_dispwidget->simple_widget_padding;
}

unsigned gfx_widgets_get_height(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return p_dispwidget->simple_widget_height;
}

unsigned gfx_widgets_get_generic_message_height(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return p_dispwidget->generic_message_height;
}

unsigned gfx_widgets_get_last_video_width(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return p_dispwidget->last_video_width;
}

unsigned gfx_widgets_get_last_video_height(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return p_dispwidget->last_video_height;
}

size_t gfx_widgets_get_msg_queue_size(void *data)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   return p_dispwidget->current_msgs_size;
}

/* Widgets list */
const static gfx_widget_t* const widgets[] = {
   &gfx_widget_screenshot,
   &gfx_widget_volume,
#ifdef HAVE_CHEEVOS
   &gfx_widget_achievement_popup,
#endif
   &gfx_widget_generic_message,
   &gfx_widget_libretro_message
};

static void msg_widget_msg_transition_animation_done(void *userdata)
{
   menu_widget_msg_t *msg = (menu_widget_msg_t*)userdata;

   if (msg->msg)
      free(msg->msg);
   msg->msg = NULL;

   if (msg->msg_new)
      msg->msg = strdup(msg->msg_new);

   msg->msg_transition_animation = 0.0f;
}

void gfx_widgets_msg_queue_push(
      void *data,
      retro_task_t *task,
      const char *msg,
      unsigned duration,
      char *title,
      enum message_queue_icon icon,
      enum message_queue_category category,
      unsigned prio, bool flush,
      bool menu_is_alive)
{
   menu_widget_msg_t    *msg_widget = NULL;
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;

   if (fifo_write_avail(p_dispwidget->msg_queue) > 0)
   {
      /* Get current msg if it exists */
      if (task && task->frontend_userdata)
      {
         msg_widget           = (menu_widget_msg_t*)task->frontend_userdata;
         /* msg_widgets can be passed between tasks */
         msg_widget->task_ptr = task;
      }

      /* Spawn a new notification */
      if (!msg_widget)
      {
         const char *title                      = msg;

         msg_widget                             = (menu_widget_msg_t*)calloc(1, sizeof(*msg_widget));

         if (task)
            title                               = task->title;

         msg_widget->duration                   = duration;
         msg_widget->offset_y                   = 0;
         msg_widget->alpha                      = 1.0f;

         msg_widget->dying                      = false;
         msg_widget->expired                    = false;

         msg_widget->expiration_timer           = 0;
         msg_widget->task_ptr                   = task;
         msg_widget->expiration_timer_started   = false;

         msg_widget->msg_new                    = NULL;
         msg_widget->msg_transition_animation   = 0.0f;

         msg_widget->text_height                = 0;

         if (p_dispwidget->msg_queue_has_icons)
         {
            msg_widget->unfolded                = false;
            msg_widget->unfolding               = false;
            msg_widget->unfold                  = 0.0f;
         }
         else
         {
            msg_widget->unfolded                = true;
            msg_widget->unfolding               = false;
            msg_widget->unfold                  = 1.0f;
         }

         if (task)
         {
            msg_widget->msg                  = strdup(title);
            msg_widget->msg_new              = strdup(title);
            msg_widget->msg_len              = (unsigned)strlen(title);

            msg_widget->task_error           = !string_is_empty(task->error);
            msg_widget->task_cancelled       = task->cancelled;
            msg_widget->task_finished        = task->finished;
            msg_widget->task_progress        = task->progress;
            msg_widget->task_ident           = task->ident;
            msg_widget->task_title_ptr       = task->title;
            msg_widget->task_count           = 1;

            msg_widget->unfolded             = true;

            msg_widget->width                = font_driver_get_message_width(
                  p_dispwidget->gfx_widget_fonts.msg_queue.font,
                  title,
                  msg_widget->msg_len, 1) +
                  p_dispwidget->simple_widget_padding / 2;

            task->frontend_userdata          = msg_widget;

            msg_widget->hourglass_rotation   = 0;
         }
         else
         {
            /* Compute rect width, wrap if necessary */
            /* Single line text > two lines text > two lines 
             * text with expanded width */
            unsigned title_length   = (unsigned)strlen(title);
            char *msg               = strdup(title);
            unsigned width          = menu_is_alive 
               ? p_dispwidget->msg_queue_default_rect_width_menu_alive 
               : p_dispwidget->msg_queue_default_rect_width;
            unsigned text_width     = font_driver_get_message_width(
                  p_dispwidget->gfx_widget_fonts.msg_queue.font,
                  title,
                  title_length,
                  1);
            msg_widget->text_height = p_dispwidget->gfx_widget_fonts.msg_queue.line_height;

            /* Text is too wide, split it into two lines */
            if (text_width > width)
            {
               /* If the second line is too short, the widget may
                * look unappealing - ensure that second line is at
                * least 25% of the total width */
               if ((text_width - (text_width >> 2)) < width)
                  width = text_width - (text_width >> 2);

               word_wrap(msg, msg, (title_length * width) / text_width,
                     false, 2);

               msg_widget->text_height *= 2;
            }
            else
               width = text_width;

            msg_widget->msg         = msg;
            msg_widget->msg_len     = (unsigned)strlen(msg);
            msg_widget->width       = width + 
               p_dispwidget->simple_widget_padding / 2;
         }

         fifo_write(p_dispwidget->msg_queue, &msg_widget, sizeof(msg_widget));
      }
      /* Update task info */
      else
      {
         if (msg_widget->expiration_timer_started)
         {
            gfx_timer_kill(&msg_widget->expiration_timer);
            msg_widget->expiration_timer_started = false;
         }

         if (!string_is_equal(task->title, msg_widget->msg_new))
         {
            unsigned len         = (unsigned)strlen(task->title);
            unsigned new_width   = font_driver_get_message_width(
                  p_dispwidget->gfx_widget_fonts.msg_queue.font,
                  task->title,
                  len,
                  1);

            if (msg_widget->msg_new)
            {
               free(msg_widget->msg_new);
               msg_widget->msg_new                 = NULL;
            }

            msg_widget->msg_new                    = strdup(task->title);
            msg_widget->msg_len                    = len;
            msg_widget->task_title_ptr             = task->title;
            msg_widget->msg_transition_animation   = 0;

            if (!task->alternative_look)
            {
               gfx_animation_ctx_entry_t entry;

               entry.easing_enum    = EASING_OUT_QUAD;
               entry.tag            = (uintptr_t)msg_widget;
               entry.duration       = MSG_QUEUE_ANIMATION_DURATION*2;
               entry.target_value   = p_dispwidget->msg_queue_height / 2.0f;
               entry.subject        = &msg_widget->msg_transition_animation;
               entry.cb             = msg_widget_msg_transition_animation_done;
               entry.userdata       = msg_widget;

               gfx_animation_push(&entry);
            }
            else
               msg_widget_msg_transition_animation_done(msg_widget);

            msg_widget->task_count++;

            msg_widget->width = new_width;
         }

         msg_widget->task_error        = !string_is_empty(task->error);
         msg_widget->task_cancelled    = task->cancelled;
         msg_widget->task_finished     = task->finished;
         msg_widget->task_progress     = task->progress;
      }
   }
}

static void gfx_widgets_unfold_end(void *userdata)
{
   menu_widget_msg_t *unfold        = (menu_widget_msg_t*)userdata;
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)dispwidget_get_ptr();

   unfold->unfolding                = false;
   p_dispwidget->widgets_moving     = false;
}

static void gfx_widgets_move_end(void *userdata)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)dispwidget_get_ptr();

   if (userdata)
   {
      gfx_animation_ctx_entry_t entry;
      menu_widget_msg_t *unfold = (menu_widget_msg_t*)userdata;

      entry.cb             = gfx_widgets_unfold_end;
      entry.duration       = MSG_QUEUE_ANIMATION_DURATION;
      entry.easing_enum    = EASING_OUT_QUAD;
      entry.subject        = &unfold->unfold;
      entry.tag            = (uintptr_t)unfold;
      entry.target_value   = 1.0f;
      entry.userdata       = unfold;

      gfx_animation_push(&entry);

      unfold->unfolded  = true;
      unfold->unfolding = true;
   }
   else
      p_dispwidget->widgets_moving = false;
}

static void gfx_widgets_msg_queue_expired(void *userdata)
{
   menu_widget_msg_t *msg = (menu_widget_msg_t *)userdata;

   if (msg && !msg->expired)
      msg->expired = true;
}

static void gfx_widgets_msg_queue_move(dispgfx_widget_t *p_dispwidget)
{
   int i;
   float y = 0;
   /* there should always be one and only one unfolded message */
   menu_widget_msg_t *unfold        = NULL; 

   SLOCK_LOCK(p_dispwidget->current_msgs_lock);

   for (i = (int)(p_dispwidget->current_msgs_size - 1); i >= 0; i--)
   {
      menu_widget_msg_t* msg = p_dispwidget->current_msgs[i];

      if (!msg || msg->dying)
         continue;

      y += p_dispwidget->msg_queue_height 
         / (msg->task_ptr ? 2 : 1) + p_dispwidget->msg_queue_spacing;

      if (!msg->unfolded)
         unfold = msg;

      if (msg->offset_y != y)
      {
         gfx_animation_ctx_entry_t entry;

         entry.cb             = (i == 0) ? gfx_widgets_move_end : NULL;
         entry.duration       = MSG_QUEUE_ANIMATION_DURATION;
         entry.easing_enum    = EASING_OUT_QUAD;
         entry.subject        = &msg->offset_y;
         entry.tag            = (uintptr_t)msg;
         entry.target_value   = y;
         entry.userdata       = unfold;

         gfx_animation_push(&entry);

         p_dispwidget->widgets_moving = true;
      }
   }

   SLOCK_UNLOCK(p_dispwidget->current_msgs_lock);
}

static void gfx_widgets_msg_queue_free(
      dispgfx_widget_t *p_dispwidget,
      menu_widget_msg_t *msg)
{
   uintptr_t tag = (uintptr_t)msg;

   if (msg->task_ptr)
   {
      /* remove the reference the task has of ourself
         only if the task is not finished already
         (finished tasks are freed before the widget) */
      if (!msg->task_finished && !msg->task_error && !msg->task_cancelled)
         msg->task_ptr->frontend_userdata = NULL;

      /* update tasks count */
      p_dispwidget->msg_queue_tasks_count--;
   }

   /* Kill all animations */
   gfx_timer_kill(&msg->hourglass_timer);
   gfx_animation_kill_by_tag(&tag);

   /* Kill all timers */
   if (msg->expiration_timer_started)
      gfx_timer_kill(&msg->expiration_timer);

   /* Free it */
   if (msg->msg)
      free(msg->msg);

   if (msg->msg_new)
      free(msg->msg_new);

   p_dispwidget->widgets_moving = false;
}

static void gfx_widgets_msg_queue_kill_end(void *userdata)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)dispwidget_get_ptr();
   menu_widget_msg_t* msg;
   unsigned i;

   SLOCK_LOCK(p_dispwidget->current_msgs_lock);

   msg = p_dispwidget->current_msgs[p_dispwidget->msg_queue_kill];
   if (msg)
   {
      /* Remove it from the list */
      for (i = p_dispwidget->msg_queue_kill; i < p_dispwidget->current_msgs_size - 1; i++)
         p_dispwidget->current_msgs[i] = p_dispwidget->current_msgs[i + 1];

      p_dispwidget->current_msgs_size--;
      p_dispwidget->current_msgs[p_dispwidget->current_msgs_size] = NULL;

      /* clean up the item */
      gfx_widgets_msg_queue_free(p_dispwidget, msg);

      /* free the associated memory */
      free(msg);
   }

   SLOCK_UNLOCK(p_dispwidget->current_msgs_lock);
}

static void gfx_widgets_msg_queue_kill(
      dispgfx_widget_t *p_dispwidget,
      unsigned idx)
{
   gfx_animation_ctx_entry_t entry;
   menu_widget_msg_t *msg = p_dispwidget->current_msgs[idx];

   if (!msg)
      return;

   p_dispwidget->widgets_moving = true;
   msg->dying                   = true;

   p_dispwidget->msg_queue_kill = idx;

   /* Drop down */
   entry.cb                     = NULL;
   entry.duration               = MSG_QUEUE_ANIMATION_DURATION;
   entry.easing_enum            = EASING_OUT_QUAD;
   entry.tag                    = (uintptr_t)msg;
   entry.userdata               = NULL;
   entry.subject                = &msg->offset_y;
   entry.target_value           = msg->offset_y - 
      p_dispwidget->msg_queue_height / 4;

   gfx_animation_push(&entry);

   /* Fade out */
   entry.cb                     = gfx_widgets_msg_queue_kill_end;
   entry.subject                = &msg->alpha;
   entry.target_value           = 0.0f;

   gfx_animation_push(&entry);

   /* Move all messages back to their correct position */
   if (p_dispwidget->current_msgs_size != 0)
      gfx_widgets_msg_queue_move(p_dispwidget);
}

void gfx_widgets_draw_icon(
      void *userdata,
      unsigned video_width,
      unsigned video_height,
      unsigned icon_width,
      unsigned icon_height,
      uintptr_t texture,
      float x, float y,
      unsigned width, unsigned height,
      float rotation, float scale_factor,
      float *color)
{
   gfx_display_ctx_rotate_draw_t rotate_draw;
   gfx_display_ctx_draw_t draw;
   struct video_coords coords;
   math_matrix_4x4 mymat;

   if (!texture)
      return;

   rotate_draw.matrix       = &mymat;
   rotate_draw.rotation     = rotation;
   rotate_draw.scale_x      = scale_factor;
   rotate_draw.scale_y      = scale_factor;
   rotate_draw.scale_z      = 1;
   rotate_draw.scale_enable = true;

   gfx_display_rotate_z(&rotate_draw, userdata);

   coords.vertices      = 4;
   coords.vertex        = NULL;
   coords.tex_coord     = NULL;
   coords.lut_tex_coord = NULL;
   coords.color         = color;

   draw.x               = x;
   draw.y               = height - y - icon_height;
   draw.width           = icon_width;
   draw.height          = icon_height;
   draw.scale_factor    = scale_factor;
   draw.rotation        = rotation;
   draw.coords          = &coords;
   draw.matrix_data     = &mymat;
   draw.texture         = texture;
   draw.prim_type       = GFX_DISPLAY_PRIM_TRIANGLESTRIP;
   draw.pipeline.id     = 0;

   gfx_display_draw(&draw, userdata,
         video_width, video_height);
}

#ifdef HAVE_TRANSLATE
static void gfx_widgets_draw_icon_blend(
      void *userdata,
      unsigned video_width,
      unsigned video_height,
      unsigned icon_width,
      unsigned icon_height,
      uintptr_t texture,
      float x, float y,
      unsigned width, unsigned height,
      float rotation, float scale_factor,
      float *color)
{
   gfx_display_ctx_rotate_draw_t rotate_draw;
   gfx_display_ctx_draw_t draw;
   struct video_coords coords;
   math_matrix_4x4 mymat;

   if (!texture)
      return;

   rotate_draw.matrix       = &mymat;
   rotate_draw.rotation     = rotation;
   rotate_draw.scale_x      = scale_factor;
   rotate_draw.scale_y      = scale_factor;
   rotate_draw.scale_z      = 1;
   rotate_draw.scale_enable = true;

   gfx_display_rotate_z(&rotate_draw, userdata);

   coords.vertices      = 4;
   coords.vertex        = NULL;
   coords.tex_coord     = NULL;
   coords.lut_tex_coord = NULL;
   coords.color         = color;

   draw.x               = x;
   draw.y               = height - y - icon_height;
   draw.width           = icon_width;
   draw.height          = icon_height;
   draw.scale_factor    = scale_factor;
   draw.rotation        = rotation;
   draw.coords          = &coords;
   draw.matrix_data     = &mymat;
   draw.texture         = texture;
   draw.prim_type       = GFX_DISPLAY_PRIM_TRIANGLESTRIP;
   draw.pipeline.id     = 0;

   gfx_display_draw_blend(&draw, userdata,
         video_width, video_height);
}
#endif

void gfx_widgets_draw_text(
      gfx_widget_font_data_t* font_data,
      const char *text,
      float x, float y,
      int width, int height,
      uint32_t color,
      enum text_alignment text_align,
      bool draw_outside)
{
   if (!font_data || string_is_empty(text))
      return;

   gfx_display_draw_text(
         font_data->font,
         text,
         x, y,
         width, height,
         color,
         text_align,
         1.0f,
         false,
         0.0f,
         draw_outside);

   font_data->usage_count++;
}

void gfx_widgets_flush_text(
      unsigned video_width, unsigned video_height,
      gfx_widget_font_data_t* font_data)
{
   /* Flushing is slow - only do it if font
    * has actually been used */
   if (!font_data || (font_data->usage_count == 0))
      return;

   font_driver_flush(video_width, video_height, font_data->font);
   font_data->raster_block.carr.coords.vertices = 0;
   font_data->usage_count                       = 0;
}

float gfx_widgets_get_thumbnail_scale_factor(
      const float dst_width, const float dst_height,
      const float image_width, const float image_height)
{
   float dst_ratio      = dst_width   / dst_height;
   float image_ratio    = image_width / image_height;

   if (dst_ratio > image_ratio)
      return (dst_height / image_height);
   return (dst_width / image_width);
}

static void gfx_widgets_start_msg_expiration_timer(
      menu_widget_msg_t *msg_widget, unsigned duration)
{
   gfx_timer_ctx_entry_t timer;

   timer.cb       = gfx_widgets_msg_queue_expired;
   timer.duration = duration;
   timer.userdata = msg_widget;

   gfx_timer_start(&msg_widget->expiration_timer, &timer);

   msg_widget->expiration_timer_started = true;
}

static void gfx_widgets_hourglass_tick(void *userdata);

static void gfx_widgets_hourglass_end(void *userdata)
{
   gfx_timer_ctx_entry_t timer;
   menu_widget_msg_t *msg  = (menu_widget_msg_t*)userdata;

   msg->hourglass_rotation = 0.0f;

   timer.cb                = gfx_widgets_hourglass_tick;
   timer.duration          = HOURGLASS_INTERVAL;
   timer.userdata          = msg;

   gfx_timer_start(&msg->hourglass_timer, &timer);
}

static void gfx_widgets_hourglass_tick(void *userdata)
{
   gfx_animation_ctx_entry_t entry;
   menu_widget_msg_t *msg = (menu_widget_msg_t*)userdata;
   uintptr_t          tag = (uintptr_t)msg;

   entry.easing_enum      = EASING_OUT_QUAD;
   entry.tag              = tag;
   entry.duration         = HOURGLASS_DURATION;
   entry.target_value     = -(2 * M_PI);
   entry.subject          = &msg->hourglass_rotation;
   entry.cb               = gfx_widgets_hourglass_end;
   entry.userdata         = msg;

   gfx_animation_push(&entry);
}

void gfx_widgets_iterate(
      void *data,
      unsigned width, unsigned height, bool fullscreen,
      const char *dir_assets, char *font_path,
      bool is_threaded)
{
   size_t i;
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)data;
   /* Check whether screen dimensions or menu scale
    * factor have changed */
   float scale_factor               = (
         gfx_display_get_driver_id() == MENU_DRIVER_ID_XMB) 
      ? gfx_display_get_widget_pixel_scale(width, height, fullscreen) 
      : gfx_display_get_widget_dpi_scale(width, height, fullscreen);

   if ((scale_factor != p_dispwidget->last_scale_factor) ||
       (width        != p_dispwidget->last_video_width) ||
       (height       != p_dispwidget->last_video_height))
   {
      p_dispwidget->last_scale_factor = scale_factor;
      p_dispwidget->last_video_width  = width;
      p_dispwidget->last_video_height = height;

      /* Note: We don't need a full context reset here
       * > Just rescale layout, and reset frame time counter */
      gfx_widgets_layout(p_dispwidget,
            is_threaded, dir_assets, font_path);
      video_driver_monitor_reset();
   }

   for (i = 0; i < ARRAY_SIZE(widgets); i++)
   {
      const gfx_widget_t* widget = widgets[i];

      if (widget->iterate)
         widget->iterate(p_dispwidget,
               width, height, fullscreen,
               dir_assets, font_path, is_threaded);
   }

   /* Messages queue */

   /* Consume one message if available */
   if ((fifo_read_avail(p_dispwidget->msg_queue) > 0)
         && !p_dispwidget->widgets_moving 
         && (p_dispwidget->current_msgs_size < ARRAY_SIZE(p_dispwidget->current_msgs)))
   {
      menu_widget_msg_t *msg_widget = NULL;

      SLOCK_LOCK(p_dispwidget->current_msgs_lock);

      if (p_dispwidget->current_msgs_size < ARRAY_SIZE(p_dispwidget->current_msgs))
      {
         if (fifo_read_avail(p_dispwidget->msg_queue) > 0)
            fifo_read(p_dispwidget->msg_queue, &msg_widget, sizeof(msg_widget));

         if (msg_widget)
         {
            /* Task messages always appear from the bottom of the screen, append it */
            if (p_dispwidget->msg_queue_tasks_count == 0 || msg_widget->task_ptr)
            {
               p_dispwidget->current_msgs[p_dispwidget->current_msgs_size] = msg_widget;
            }
            /* Regular messages are always above tasks, make room and insert it */
            else
            {
               unsigned idx = (unsigned)(p_dispwidget->current_msgs_size -
                  p_dispwidget->msg_queue_tasks_count);
               for (i = p_dispwidget->current_msgs_size; i > idx; i--)
                  p_dispwidget->current_msgs[i] = p_dispwidget->current_msgs[i - 1];
               p_dispwidget->current_msgs[idx] = msg_widget;
            }

            p_dispwidget->current_msgs_size++;
         }
      }

      SLOCK_UNLOCK(p_dispwidget->current_msgs_lock);

      if (msg_widget)
      {
         /* Start expiration timer if not associated to a task */
         if (!msg_widget->task_ptr)
         {
            if (!msg_widget->expiration_timer_started)
               gfx_widgets_start_msg_expiration_timer(
                  msg_widget, MSG_QUEUE_ANIMATION_DURATION * 2
                  + msg_widget->duration);
         }
         /* Else, start hourglass animation timer */
         else
         {
            p_dispwidget->msg_queue_tasks_count++;
            gfx_widgets_hourglass_end(msg_widget);
         }

         if (p_dispwidget->current_msgs_size != 0)
            gfx_widgets_msg_queue_move(p_dispwidget);
      }
   }

   /* Kill first expired message */
   /* Start expiration timer of dead tasks */
   for (i = 0; i < p_dispwidget->current_msgs_size; i++)
   {
      menu_widget_msg_t *msg_widget = p_dispwidget->current_msgs[i];

      if (!msg_widget)
         continue;

      if (msg_widget->task_ptr && (msg_widget->task_finished 
               || msg_widget->task_cancelled))
         if (!msg_widget->expiration_timer_started)
            gfx_widgets_start_msg_expiration_timer(msg_widget, TASK_FINISHED_DURATION);

      if (msg_widget->expired && !p_dispwidget->widgets_moving)
      {
         gfx_widgets_msg_queue_kill(p_dispwidget,
               (unsigned)i);
         break;
      }
   }
}

static int gfx_widgets_draw_indicator(
      dispgfx_widget_t *p_dispwidget,
      void *userdata, 
      unsigned video_width,
      unsigned video_height,
      uintptr_t icon, int y, int top_right_x_advance, 
      enum msg_hash_enums msg)
{
   unsigned width;

   gfx_display_set_alpha(gfx_widgets_backdrop_orig, DEFAULT_BACKDROP);

   if (icon)
   {
      unsigned height = p_dispwidget->simple_widget_height * 2;
      width  = height;

      gfx_display_draw_quad(userdata,
         video_width, video_height,
         top_right_x_advance - width, y,
         width, height,
         video_width, video_height,
         gfx_widgets_backdrop_orig
      );

      gfx_display_set_alpha(gfx_widgets_pure_white, 1.0f);

      gfx_display_blend_begin(userdata);
      gfx_widgets_draw_icon(
            userdata,
            video_width,
            video_height,
            width, height,
            icon, top_right_x_advance - width, y,
            video_width, video_height,
            0, 1, gfx_widgets_pure_white
            );
      gfx_display_blend_end(userdata);
   }
   else
   {
      unsigned height       = p_dispwidget->simple_widget_height;
      const char *txt       = msg_hash_to_str(msg);

      width = font_driver_get_message_width(p_dispwidget->gfx_widget_fonts.regular.font,
            txt,
            (unsigned)strlen(txt), 1) + p_dispwidget->simple_widget_padding * 2;

      gfx_display_draw_quad(userdata,
            video_width, video_height,
            top_right_x_advance - width, y,
            width, height,
            video_width, video_height,
            gfx_widgets_backdrop_orig
      );

      gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.regular,
            txt,
            top_right_x_advance - width 
            + p_dispwidget->simple_widget_padding,
            y + (height / 2.0f) + 
            p_dispwidget->gfx_widget_fonts.regular.line_centre_offset,
            video_width, video_height,
            0xFFFFFFFF, TEXT_ALIGN_LEFT,
            false);
   }

   return width;
}

static void gfx_widgets_draw_task_msg(
      dispgfx_widget_t *p_dispwidget,
      menu_widget_msg_t *msg,
      void *userdata,
      unsigned video_width,
      unsigned video_height)
{
   unsigned text_color;
   unsigned bar_width;

   unsigned rect_x;
   unsigned rect_y;
   unsigned rect_width;
   unsigned rect_height;
   float text_y_base;

   float *msg_queue_current_background;
   float *msg_queue_current_bar;

   bool draw_msg_new                = false;
   unsigned task_percentage_offset  = 0;
   char task_percentage[256]        = {0};

   if (msg->msg_new)
      draw_msg_new = !string_is_equal(msg->msg_new, msg->msg);

   task_percentage_offset = p_dispwidget->gfx_widget_fonts.msg_queue.glyph_width * (msg->task_error ? 12 : 5) 
      + p_dispwidget->simple_widget_padding * 1.25f; /*11 = strlen("Task failed")+1 */

   if (msg->task_finished)
   {
      if (msg->task_error)
         strlcpy(task_percentage, "Task failed", sizeof(task_percentage));
      else
         strlcpy(task_percentage, " ", sizeof(task_percentage));
   }
   else if (msg->task_progress >= 0 && msg->task_progress <= 100)
      snprintf(task_percentage, sizeof(task_percentage),
            "%i%%", msg->task_progress);

   rect_width = p_dispwidget->simple_widget_padding 
      + msg->width 
      + task_percentage_offset;
   bar_width  = rect_width * msg->task_progress/100.0f;
   text_color = COLOR_TEXT_ALPHA(0xFFFFFF00, (unsigned)(msg->alpha*255.0f));

   /* Rect */
   if (msg->task_finished)
      if (msg->task_count == 1)
         msg_queue_current_background = msg_queue_task_progress_1;
      else
         msg_queue_current_background = msg_queue_task_progress_2;
   else
      if (msg->task_count == 1)
         msg_queue_current_background = msg_queue_background;
      else
         msg_queue_current_background = msg_queue_task_progress_1;

   rect_x      = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x;
   rect_y      = video_height - msg->offset_y;
   rect_height = p_dispwidget->msg_queue_height / 2;

   gfx_display_set_alpha(msg_queue_current_background, msg->alpha);
   gfx_display_draw_quad(userdata,
         video_width, video_height,
         rect_x, rect_y,
         rect_width, rect_height,
         video_width, video_height,
         msg_queue_current_background
         );

   /* Progress bar */
   if (!msg->task_finished && msg->task_progress >= 0 && msg->task_progress <= 100)
   {
      if (msg->task_count == 1)
         msg_queue_current_bar = msg_queue_task_progress_1;
      else
         msg_queue_current_bar = msg_queue_task_progress_2;

      gfx_display_set_alpha(msg_queue_current_bar, 1.0f);
      gfx_display_draw_quad(userdata,
            video_width, video_height,
            p_dispwidget->msg_queue_task_rect_start_x, video_height - msg->offset_y,
            bar_width, rect_height,
            video_width, video_height,
            msg_queue_current_bar
            );
   }

   /* Icon */
   gfx_display_set_alpha(gfx_widgets_pure_white, msg->alpha);
   gfx_display_blend_begin(userdata);
   gfx_widgets_draw_icon(
         userdata,
         video_width,
         video_height,
         p_dispwidget->msg_queue_height / 2,
         p_dispwidget->msg_queue_height / 2,
         p_dispwidget->gfx_widgets_icons_textures[
         msg->task_finished 
         ? MENU_WIDGETS_ICON_CHECK 
         : MENU_WIDGETS_ICON_HOURGLASS],
         p_dispwidget->msg_queue_task_hourglass_x,
         video_height - msg->offset_y,
         video_width,
         video_height,
         msg->task_finished ? 0 : msg->hourglass_rotation,
         1, gfx_widgets_pure_white);
   gfx_display_blend_end(userdata);

   /* Text */
   text_y_base = video_height 
      - msg->offset_y 
      + p_dispwidget->msg_queue_height / 4.0f 
      + p_dispwidget->gfx_widget_fonts.msg_queue.line_centre_offset;

   if (draw_msg_new)
   {
      gfx_widgets_flush_text(video_width, video_height,
            &p_dispwidget->gfx_widget_fonts.msg_queue);

      gfx_display_scissor_begin(userdata,
            video_width, video_height,
            rect_x, rect_y, rect_width, rect_height);

      gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
            msg->msg_new,
            p_dispwidget->msg_queue_task_text_start_x,
            text_y_base 
            - p_dispwidget->msg_queue_height / 2.0f 
            + msg->msg_transition_animation,
            video_width, video_height,
            text_color,
            TEXT_ALIGN_LEFT,
            true);
   }

   gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
         msg->msg,
         p_dispwidget->msg_queue_task_text_start_x,
         text_y_base + msg->msg_transition_animation,
         video_width, video_height,
         text_color,
         TEXT_ALIGN_LEFT,
         true);

   if (draw_msg_new)
   {
      gfx_widgets_flush_text(video_width, video_height,
            &p_dispwidget->gfx_widget_fonts.msg_queue);
      gfx_display_scissor_end(userdata,
            video_width, video_height);
   }

   /* Progress text */
   text_color = COLOR_TEXT_ALPHA(0xFFFFFF00, (unsigned)(msg->alpha/2*255.0f));
   gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
      task_percentage,
      p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x + rect_width - 
      p_dispwidget->gfx_widget_fonts.msg_queue.glyph_width,
      text_y_base,
      video_width, video_height,
      text_color,
      TEXT_ALIGN_RIGHT,
      true);
}

static void gfx_widgets_draw_regular_msg(
      dispgfx_widget_t *p_dispwidget,
      menu_widget_msg_t *msg,
      void *userdata,
      unsigned video_width,
      unsigned video_height)
{
   unsigned bar_width;
   unsigned text_color;
   uintptr_t            icon        = 0;

   if (!icon)
      icon = p_dispwidget->gfx_widgets_icons_textures[
         MENU_WIDGETS_ICON_INFO]; /* TODO: Real icon logic here */

   /* Icon */
   gfx_display_set_alpha(msg_queue_info, msg->alpha);
   gfx_display_set_alpha(gfx_widgets_pure_white, msg->alpha);
   gfx_display_set_alpha(msg_queue_background, msg->alpha);

   if (!msg->unfolded || msg->unfolding)
   {
      gfx_widgets_flush_text(video_width, video_height,
            &p_dispwidget->gfx_widget_fonts.regular);
      gfx_widgets_flush_text(video_width, video_height,
            &p_dispwidget->gfx_widget_fonts.bold);
      gfx_widgets_flush_text(video_width, video_height,
            &p_dispwidget->gfx_widget_fonts.msg_queue);

      gfx_display_scissor_begin(userdata,
            video_width, video_height,
            p_dispwidget->msg_queue_scissor_start_x, 0,
            (p_dispwidget->msg_queue_scissor_start_x + msg->width - 
             p_dispwidget->simple_widget_padding * 2) 
            * msg->unfold, video_height);
   }

   if (p_dispwidget->msg_queue_has_icons)
   {
      gfx_display_blend_begin(userdata);
      /* (int) cast is to be consistent with the rect drawing 
       * and prevent alignment issues, don't remove it */
      gfx_widgets_draw_icon(
            userdata,
            video_width,
            video_height,
            p_dispwidget->msg_queue_icon_size_x,
            p_dispwidget->msg_queue_icon_size_y,
            p_dispwidget->msg_queue_icon_rect,
            p_dispwidget->msg_queue_spacing,
            (int)(video_height - msg->offset_y - p_dispwidget->msg_queue_icon_offset_y),
            video_width, video_height, 
            0, 1, msg_queue_background);

      gfx_display_blend_end(userdata);
   }

   /* Background */
   bar_width = p_dispwidget->simple_widget_padding + msg->width;

   gfx_display_draw_quad(
         userdata,
         video_width,
         video_height,
         p_dispwidget->msg_queue_rect_start_x,
         video_height - msg->offset_y,
         bar_width,
         p_dispwidget->msg_queue_height,
         video_width,
         video_height,
         msg_queue_background
         );

   /* Text */
   text_color = COLOR_TEXT_ALPHA(0xFFFFFF00, (unsigned)(msg->alpha*255.0f));

   gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
      msg->msg,
      p_dispwidget->msg_queue_regular_text_start - ((1.0f-msg->unfold) * msg->width/2),
      video_height - msg->offset_y + (p_dispwidget->msg_queue_height - msg->text_height)/2.0f + p_dispwidget->gfx_widget_fonts.msg_queue.line_ascender,
      video_width, video_height,
      text_color,
      TEXT_ALIGN_LEFT,
      true);

   if (!msg->unfolded || msg->unfolding)
   {
      gfx_widgets_flush_text(video_width, video_height, &p_dispwidget->gfx_widget_fonts.regular);
      gfx_widgets_flush_text(video_width, video_height, &p_dispwidget->gfx_widget_fonts.bold);
      gfx_widgets_flush_text(video_width, video_height, &p_dispwidget->gfx_widget_fonts.msg_queue);

      gfx_display_scissor_end(userdata,
            video_width, video_height);
   }

   if (p_dispwidget->msg_queue_has_icons)
   {
      gfx_display_blend_begin(userdata);

      gfx_widgets_draw_icon(
            userdata,
            video_width,
            video_height,
            p_dispwidget->msg_queue_icon_size_x,
            p_dispwidget->msg_queue_icon_size_y,
            p_dispwidget->msg_queue_icon,
            p_dispwidget->msg_queue_spacing, video_height 
            - msg->offset_y - p_dispwidget->msg_queue_icon_offset_y, 
            video_width, video_height,
            0, 1, msg_queue_info);

      gfx_widgets_draw_icon(
            userdata,
            video_width,
            video_height,
            p_dispwidget->msg_queue_icon_size_x,
            p_dispwidget->msg_queue_icon_size_y,
            p_dispwidget->msg_queue_icon_outline,
            p_dispwidget->msg_queue_spacing,
            video_height - msg->offset_y - p_dispwidget->msg_queue_icon_offset_y, 
            video_width, video_height,
            0, 1, gfx_widgets_pure_white);

      gfx_widgets_draw_icon(
            userdata,
            video_width,
            video_height,
            p_dispwidget->msg_queue_internal_icon_size, p_dispwidget->msg_queue_internal_icon_size,
            icon, p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_internal_icon_offset,
            video_height - msg->offset_y - p_dispwidget->msg_queue_icon_offset_y + p_dispwidget->msg_queue_internal_icon_offset, 
            video_width, video_height,
            0, 1, gfx_widgets_pure_white);

      gfx_display_blend_end(userdata);
   }
}

#ifdef HAVE_MENU
static void gfx_widgets_draw_backdrop(
      void *userdata,
      unsigned video_width,
      unsigned video_height,
      float alpha)
{
   gfx_display_set_alpha(gfx_widgets_backdrop, alpha);
   gfx_display_draw_quad(userdata,
         video_width, video_height, 0, 0,
         video_width, video_height, video_width, video_height,
         gfx_widgets_backdrop);
}

static void gfx_widgets_draw_load_content_animation(
      dispgfx_widget_t *p_dispwidget,
      void *userdata,
      unsigned video_width,
      unsigned video_height)
{
   /* TODO: change metrics? */
   int icon_size         = (int)p_dispwidget->load_content_animation_icon_size;
   uint32_t text_alpha   = p_dispwidget->load_content_animation_fade_alpha * 255.0f;
   uint32_t text_color   = COLOR_TEXT_ALPHA(0xB8B8B800, text_alpha);
   unsigned text_offset  = -25 * p_dispwidget->last_scale_factor * 
      p_dispwidget->load_content_animation_fade_alpha;
   float *icon_color     = p_dispwidget->load_content_animation_icon_color;

   /* Fade out */
   gfx_widgets_draw_backdrop(userdata,
         video_width, video_height,
         p_dispwidget->load_content_animation_fade_alpha);

   /* Icon */
   gfx_display_set_alpha(icon_color,
         p_dispwidget->load_content_animation_icon_alpha);
   gfx_display_blend_begin(userdata);
   gfx_widgets_draw_icon(
         userdata,
         video_width,
         video_height,
         icon_size,
         icon_size,
         p_dispwidget->load_content_animation_icon,
         video_width  / 2 - icon_size/2,
         video_height / 2 - icon_size/2,
         video_width,
         video_height,
         0, 1, icon_color
         );
   gfx_display_blend_end(userdata);

   /* Text
    * TODO/FIXME: Check vertical alignment - where is
    * this text actually meant to go...? */
   gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.bold,
         p_dispwidget->load_content_animation_content_name,
         video_width  / 2,
         video_height / 2 + (175 + 25) * p_dispwidget->last_scale_factor 
         + text_offset 
         + p_dispwidget->gfx_widget_fonts.bold.line_centre_offset,
         video_width,
         video_height,
         text_color,
         TEXT_ALIGN_CENTER,
         false);

   /* Flush text layer */
   gfx_widgets_flush_text(video_width, video_height,
         &p_dispwidget->gfx_widget_fonts.regular);
   gfx_widgets_flush_text(video_width, video_height,
         &p_dispwidget->gfx_widget_fonts.bold);
   gfx_widgets_flush_text(video_width, video_height,
         &p_dispwidget->gfx_widget_fonts.msg_queue);

   /* Everything disappears */
   gfx_widgets_draw_backdrop(userdata,
         video_width, video_height,
         p_dispwidget->load_content_animation_final_fade_alpha);
}
#endif

static void INLINE gfx_widgets_font_bind(gfx_widget_font_data_t *font_data)
{
   font_driver_bind_block(font_data->font, &font_data->raster_block);
   font_data->raster_block.carr.coords.vertices = 0;
   font_data->usage_count                       = 0;
}

static void INLINE gfx_widgets_font_unbind(gfx_widget_font_data_t *font_data)
{
   font_driver_bind_block(font_data->font, NULL);
}

void gfx_widgets_frame(void *data)
{
   size_t i;
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)dispwidget_get_ptr();
   video_frame_info_t *video_info   = (video_frame_info_t*)data;
   bool framecount_show             = video_info->framecount_show;
   bool memory_show                 = video_info->memory_show;
   bool core_status_msg_show        = video_info->core_status_msg_show;
   void *userdata                   = video_info->userdata;
   unsigned video_width             = video_info->width;
   unsigned video_height            = video_info->height;
   bool widgets_is_paused           = video_info->widgets_is_paused;
   bool fps_show                    = video_info->fps_show;
   bool widgets_is_fastforwarding   = video_info->widgets_is_fast_forwarding;
   bool widgets_is_rewinding        = video_info->widgets_is_rewinding;
   bool runloop_is_slowmotion       = video_info->runloop_is_slowmotion;
   int top_right_x_advance          = video_width;

   p_dispwidget->gfx_widgets_frame_count++;

   gfx_display_set_viewport(video_width, video_height);

   /* Font setup */
   gfx_widgets_font_bind(&p_dispwidget->gfx_widget_fonts.regular);
   gfx_widgets_font_bind(&p_dispwidget->gfx_widget_fonts.bold);
   gfx_widgets_font_bind(&p_dispwidget->gfx_widget_fonts.msg_queue);

#ifdef HAVE_TRANSLATE
   /* AI Service overlay */
   if (p_dispwidget->ai_service_overlay_state > 0)
   {
      float outline_color[16] = {
      0.00, 1.00, 0.00, 1.00,
      0.00, 1.00, 0.00, 1.00,
      0.00, 1.00, 0.00, 1.00,
      0.00, 1.00, 0.00, 1.00,
      };
      gfx_display_set_alpha(gfx_widgets_pure_white, 1.0f);

      gfx_widgets_draw_icon_blend(
            userdata,
            video_width,
            video_height,
            video_width,
            video_height,
            p_dispwidget->ai_service_overlay_texture,
            0,
            0,
            video_width,
            video_height,
            0,
            1,
            gfx_widgets_pure_white
            );
      /* top line */
      gfx_display_draw_quad(userdata,
            video_width, video_height,
            0, 0,
            video_width,
            p_dispwidget->divider_width_1px,
            video_width,
            video_height,
            outline_color
            );
      /* bottom line */
      gfx_display_draw_quad(userdata,
            video_width, video_height,
            0,
            video_height - p_dispwidget->divider_width_1px,
            video_width,
            p_dispwidget->divider_width_1px,
            video_width,
            video_height,
            outline_color
            );
      /* left line */
      gfx_display_draw_quad(userdata,
            video_width,
            video_height,
            0,
            0,
            p_dispwidget->divider_width_1px,
            video_height,
            video_width,
            video_height,
            outline_color
            );
      /* right line */
      gfx_display_draw_quad(userdata,
            video_width, video_height,
            video_width - p_dispwidget->divider_width_1px,
            0,
            p_dispwidget->divider_width_1px,
            video_height,
            video_width,
            video_height,
            outline_color
            );

      if (p_dispwidget->ai_service_overlay_state == 2)
          p_dispwidget->ai_service_overlay_state = 3;
   }
#endif

   /* FPS Counter */
   if (     fps_show 
         || framecount_show
         || memory_show
         || core_status_msg_show
         )
   {
      const char *text      = *p_dispwidget->gfx_widgets_fps_text == '\0' 
         ? msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE) 
         : p_dispwidget->gfx_widgets_fps_text;

      int text_width        = font_driver_get_message_width(
            p_dispwidget->gfx_widget_fonts.regular.font,
            text,
            (unsigned)strlen(text), 1.0f);
      int total_width       = text_width 
         + p_dispwidget->simple_widget_padding * 2;

      int fps_text_x        = top_right_x_advance 
         - p_dispwidget->simple_widget_padding - text_width;
      /* Ensure that left hand side of text does
       * not bleed off the edge of the screen */
      fps_text_x            = (fps_text_x < 0) ? 0 : fps_text_x;

      gfx_display_set_alpha(gfx_widgets_backdrop_orig, DEFAULT_BACKDROP);

      gfx_display_draw_quad(userdata,
            video_width,
            video_height,
            top_right_x_advance - total_width, 0,
            total_width,
            p_dispwidget->simple_widget_height,
            video_width,
            video_height,
            gfx_widgets_backdrop_orig
            );

      gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.regular,
            text,
            fps_text_x,
            p_dispwidget->simple_widget_height / 2.0f 
            + p_dispwidget->gfx_widget_fonts.regular.line_centre_offset,
            video_width, video_height,
            0xFFFFFFFF,
            TEXT_ALIGN_LEFT,
            true);
   }

   /* Indicators */
   if (widgets_is_paused)
      top_right_x_advance -= gfx_widgets_draw_indicator(
            p_dispwidget,
            userdata,
            video_width,
            video_height,
            p_dispwidget->gfx_widgets_icons_textures[
            MENU_WIDGETS_ICON_PAUSED],
            (fps_show 
             ? p_dispwidget->simple_widget_height 
             : 0),
            top_right_x_advance,
            MSG_PAUSED);

   if (widgets_is_fastforwarding)
      top_right_x_advance -= gfx_widgets_draw_indicator(
            p_dispwidget,
            userdata,
            video_width,
            video_height,
            p_dispwidget->gfx_widgets_icons_textures[
            MENU_WIDGETS_ICON_FAST_FORWARD],
            (fps_show ? p_dispwidget->simple_widget_height : 0),
            top_right_x_advance,
            MSG_FAST_FORWARD);

   if (widgets_is_rewinding)
      top_right_x_advance -= gfx_widgets_draw_indicator(
            p_dispwidget,
            userdata,
            video_width,
            video_height,
            p_dispwidget->gfx_widgets_icons_textures[
            MENU_WIDGETS_ICON_REWIND],
            (fps_show ? p_dispwidget->simple_widget_height : 0),
            top_right_x_advance,
            MSG_REWINDING);

   if (runloop_is_slowmotion)
      top_right_x_advance -= gfx_widgets_draw_indicator(
            p_dispwidget,
            userdata,
            video_width,
            video_height,
            p_dispwidget->gfx_widgets_icons_textures[
            MENU_WIDGETS_ICON_SLOW_MOTION],
            (fps_show ? p_dispwidget->simple_widget_height : 0),
            top_right_x_advance,
            MSG_SLOW_MOTION);

   for (i = 0; i < ARRAY_SIZE(widgets); i++)
   {
      const gfx_widget_t* widget = widgets[i];

      if (widget->frame)
         widget->frame(data, p_dispwidget);
   }

   /* Draw all messages */
   if (p_dispwidget->current_msgs_size)
   {
      SLOCK_LOCK(p_dispwidget->current_msgs_lock);

      for (i = 0; i < p_dispwidget->current_msgs_size; i++)
      {
         menu_widget_msg_t* msg = p_dispwidget->current_msgs[i];

         if (!msg)
            continue;

         if (msg->task_ptr)
            gfx_widgets_draw_task_msg(
               p_dispwidget,
               msg, userdata,
               video_width, video_height);
         else
            gfx_widgets_draw_regular_msg(
               p_dispwidget,
               msg, userdata,
               video_width, video_height);
      }

      SLOCK_UNLOCK(p_dispwidget->current_msgs_lock);
   }

#ifdef HAVE_MENU
   /* Load content animation */
   if (p_dispwidget->load_content_animation_running)
      gfx_widgets_draw_load_content_animation(
            p_dispwidget,
            userdata,
            video_width,
            video_height);
   else
#endif
   {
      gfx_widgets_flush_text(video_width, video_height,
            &p_dispwidget->gfx_widget_fonts.regular);
      gfx_widgets_flush_text(video_width, video_height,
            &p_dispwidget->gfx_widget_fonts.bold);
      gfx_widgets_flush_text(video_width, video_height,
            &p_dispwidget->gfx_widget_fonts.msg_queue);
   }

   /* Unbind fonts */
   gfx_widgets_font_unbind(&p_dispwidget->gfx_widget_fonts.regular);
   gfx_widgets_font_unbind(&p_dispwidget->gfx_widget_fonts.bold);
   gfx_widgets_font_unbind(&p_dispwidget->gfx_widget_fonts.msg_queue);

   gfx_display_unset_viewport(video_width, video_height);
}

bool gfx_widgets_init(uintptr_t widgets_active_ptr,
      bool video_is_threaded,
      unsigned width, unsigned height, bool fullscreen,
      const char *dir_assets, char *font_path)
{
   dispgfx_widget_t *p_dispwidget              = (dispgfx_widget_t*)
      dispwidget_get_ptr();
   p_dispwidget->divider_width_1px             = 1;
   p_dispwidget->gfx_widgets_generic_tag       = (uintptr_t)widgets_active_ptr;

   if (!gfx_display_init_first_driver(video_is_threaded))
      goto error;

   if (!p_dispwidget->widgets_inited)
   {
      size_t i;

      p_dispwidget->gfx_widgets_frame_count = 0;

      for (i = 0; i < ARRAY_SIZE(widgets); i++)
      {
         const gfx_widget_t* widget = widgets[i];

         if (widget->init)
            widget->init(video_is_threaded, fullscreen);
      }

      p_dispwidget->msg_queue = 
         fifo_new(MSG_QUEUE_PENDING_MAX * sizeof(menu_widget_msg_t*));

      if (!p_dispwidget->msg_queue)
         goto error;

      memset(&p_dispwidget->current_msgs[0], 0, sizeof(p_dispwidget->current_msgs));
      p_dispwidget->current_msgs_size = 0;

#ifdef HAVE_THREADS
      p_dispwidget->current_msgs_lock = slock_new();
#endif

      p_dispwidget->widgets_inited = true;
   }

   gfx_widgets_context_reset(
         p_dispwidget,
         video_is_threaded,
         width, height, fullscreen,
         dir_assets, font_path);

   return true;

error:
   gfx_widgets_free(p_dispwidget);
   return false;
}

bool gfx_widgets_deinit(bool widgets_persisting)
{
   dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)dispwidget_get_ptr();
   if (!p_dispwidget->widgets_inited)
      return false;

   gfx_widgets_context_destroy(p_dispwidget);

   if (!widgets_persisting)
      gfx_widgets_free(p_dispwidget);

   return true;
}

static void gfx_widgets_font_init(
      dispgfx_widget_t *p_dispwidget,
      gfx_widget_font_data_t *font_data,
      bool is_threaded, char *font_path, float font_size)
{
   int                glyph_width   = 0;
   float                scaled_size = font_size * 
      p_dispwidget->last_scale_factor;

   /* Free existing font */
   if (font_data->font)
   {
      gfx_display_font_free(font_data->font);
      font_data->font = NULL;
   }

   /* Get approximate glyph width */
   font_data->glyph_width = scaled_size * (3.0f / 4.0f);

   /* Create font */
   font_data->font = gfx_display_font_file(font_path, scaled_size, is_threaded);

   /* Get font metadata */
   glyph_width = font_driver_get_message_width(font_data->font, "a", 1, 1.0f);
   if (glyph_width > 0)
      font_data->glyph_width     = (float)glyph_width;
   font_data->line_height        = (float)font_driver_get_line_height(font_data->font, 1.0f);
   font_data->line_ascender      = (float)font_driver_get_line_ascender(font_data->font, 1.0f);
   font_data->line_descender     = (float)font_driver_get_line_descender(font_data->font, 1.0f);
   font_data->line_centre_offset = (float)font_driver_get_line_centre_offset(font_data->font, 1.0f);

   font_data->usage_count = 0;
}

static void gfx_widgets_layout(
      dispgfx_widget_t *p_dispwidget,
      bool is_threaded, const char *dir_assets, char *font_path)
{
   size_t i;

   /* Initialise fonts */
   if (string_is_empty(font_path))
   {
      char ozone_path[PATH_MAX_LENGTH];
      char font_file[PATH_MAX_LENGTH];

      ozone_path[0] = '\0';
      font_file[0]  = '\0';

      /* Base path */
      fill_pathname_join(ozone_path, dir_assets, "ozone", sizeof(ozone_path));

      /* Create regular font */
      fill_pathname_join(font_file, ozone_path, "regular.ttf", sizeof(font_file));
      gfx_widgets_font_init(p_dispwidget,
            &p_dispwidget->gfx_widget_fonts.regular,
            is_threaded, font_file, BASE_FONT_SIZE);

      /* Create bold font */
      fill_pathname_join(font_file, ozone_path, "bold.ttf", sizeof(font_file));
      gfx_widgets_font_init(p_dispwidget,
            &p_dispwidget->gfx_widget_fonts.bold,
            is_threaded, font_file, BASE_FONT_SIZE);

      /* Create msg_queue font */
      fill_pathname_join(font_file, ozone_path, "regular.ttf", sizeof(font_file));
      gfx_widgets_font_init(p_dispwidget,
            &p_dispwidget->gfx_widget_fonts.msg_queue,
            is_threaded, font_file, MSG_QUEUE_FONT_SIZE);
   }
   else
   {
      /* Load fonts from user-supplied path */
      gfx_widgets_font_init(p_dispwidget,
            &p_dispwidget->gfx_widget_fonts.regular,
            is_threaded, font_path, BASE_FONT_SIZE);
      gfx_widgets_font_init(p_dispwidget,
            &p_dispwidget->gfx_widget_fonts.bold,
            is_threaded, font_path, BASE_FONT_SIZE);
      gfx_widgets_font_init(p_dispwidget,
            &p_dispwidget->gfx_widget_fonts.msg_queue,
            is_threaded, font_path, MSG_QUEUE_FONT_SIZE);
   }

   /* Calculate dimensions */
   p_dispwidget->simple_widget_padding            = p_dispwidget->gfx_widget_fonts.regular.line_height * 2.0f/3.0f;
   p_dispwidget->simple_widget_height             = p_dispwidget->gfx_widget_fonts.regular.line_height + p_dispwidget->simple_widget_padding;

   p_dispwidget->msg_queue_height                 = p_dispwidget->gfx_widget_fonts.msg_queue.line_height * 2.5f * (BASE_FONT_SIZE / MSG_QUEUE_FONT_SIZE);

   if (p_dispwidget->msg_queue_has_icons)
   {
      p_dispwidget->msg_queue_icon_size_y         = p_dispwidget->msg_queue_height 
         * 1.2347826087f; /* original image is 280x284 */
      p_dispwidget->msg_queue_icon_size_x         = 0.98591549295f * p_dispwidget->msg_queue_icon_size_y;
   }
   else
   {
      p_dispwidget->msg_queue_icon_size_x         = 0;
      p_dispwidget->msg_queue_icon_size_y         = 0;
   }

   p_dispwidget->msg_queue_spacing                = p_dispwidget->msg_queue_height / 3;
   p_dispwidget->msg_queue_rect_start_x           = p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_icon_size_x;
   p_dispwidget->msg_queue_internal_icon_size     = p_dispwidget->msg_queue_icon_size_y;
   p_dispwidget->msg_queue_internal_icon_offset   = (p_dispwidget->msg_queue_icon_size_y - p_dispwidget->msg_queue_internal_icon_size) / 2;
   p_dispwidget->msg_queue_icon_offset_y          = (p_dispwidget->msg_queue_icon_size_y - p_dispwidget->msg_queue_height) / 2;
   p_dispwidget->msg_queue_scissor_start_x        = p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_icon_size_x - (p_dispwidget->msg_queue_icon_size_x * 0.28928571428f);

   if (p_dispwidget->msg_queue_has_icons)
      p_dispwidget->msg_queue_regular_padding_x   = p_dispwidget->simple_widget_padding / 2;
   else
      p_dispwidget->msg_queue_regular_padding_x   = p_dispwidget->simple_widget_padding;

   p_dispwidget->msg_queue_task_rect_start_x      = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x;

   p_dispwidget->msg_queue_task_text_start_x      = p_dispwidget->msg_queue_task_rect_start_x + p_dispwidget->msg_queue_height / 2;

   if (!p_dispwidget->gfx_widgets_icons_textures[MENU_WIDGETS_ICON_HOURGLASS])
      p_dispwidget->msg_queue_task_text_start_x         -= 
         p_dispwidget->gfx_widget_fonts.msg_queue.glyph_width * 2.0f;

   p_dispwidget->msg_queue_regular_text_start            = p_dispwidget->msg_queue_rect_start_x + p_dispwidget->msg_queue_regular_padding_x;

   p_dispwidget->msg_queue_task_hourglass_x              = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x;

   p_dispwidget->generic_message_height    = p_dispwidget->gfx_widget_fonts.regular.line_height * 2.0f;

   p_dispwidget->msg_queue_default_rect_width_menu_alive = p_dispwidget
      ->gfx_widget_fonts.msg_queue.glyph_width * 40.0f;
   p_dispwidget->msg_queue_default_rect_width            = p_dispwidget->last_video_width 
      - p_dispwidget->msg_queue_regular_text_start - (2 * p_dispwidget->simple_widget_padding);

#ifdef HAVE_MENU
   p_dispwidget->load_content_animation_icon_size_initial = 
      LOAD_CONTENT_ANIMATION_INITIAL_ICON_SIZE * 
      p_dispwidget->last_scale_factor;
   p_dispwidget->load_content_animation_icon_size_target  = 
      LOAD_CONTENT_ANIMATION_TARGET_ICON_SIZE  * 
      p_dispwidget->last_scale_factor;
#endif

   p_dispwidget->divider_width_1px    = 1;
   if (p_dispwidget->last_scale_factor > 1.0f)
      p_dispwidget->divider_width_1px = (unsigned)(p_dispwidget->last_scale_factor + 0.5f);

   for (i = 0; i < ARRAY_SIZE(widgets); i++)
   {
      const gfx_widget_t* widget = widgets[i];

      if (widget->layout)
         widget->layout(p_dispwidget,
               is_threaded, dir_assets, font_path);
   }
}

static void gfx_widgets_context_reset(
      dispgfx_widget_t *p_dispwidget,
      bool is_threaded,
      unsigned width, unsigned height, bool fullscreen,
      const char *dir_assets, char *font_path)
{
   size_t i;
   char xmb_path[PATH_MAX_LENGTH];
   char monochrome_png_path[PATH_MAX_LENGTH];
   char gfx_widgets_path[PATH_MAX_LENGTH];
   char theme_path[PATH_MAX_LENGTH];

   xmb_path[0]            = '\0';
   monochrome_png_path[0] = '\0';
   gfx_widgets_path[0]    = '\0';
   theme_path[0]          = '\0';

   /* Textures paths */
   fill_pathname_join(
      gfx_widgets_path,
      dir_assets,
      "menu_widgets",
      sizeof(gfx_widgets_path)
   );

   fill_pathname_join(
      xmb_path,
      dir_assets,
      "xmb",
      sizeof(xmb_path)
   );

   /* Monochrome */
   fill_pathname_join(
      theme_path,
      xmb_path,
      "monochrome",
      sizeof(theme_path)
   );

   fill_pathname_join(
      monochrome_png_path,
      theme_path,
      "png",
      sizeof(monochrome_png_path)
   );

   /* Load textures */
   /* Icons */
   for (i = 0; i < MENU_WIDGETS_ICON_LAST; i++)
   {
      gfx_display_reset_textures_list(
            gfx_widgets_icons_names[i],
            monochrome_png_path,
            &p_dispwidget->gfx_widgets_icons_textures[i],
            TEXTURE_FILTER_MIPMAP_LINEAR,
            NULL,
            NULL);
   }

   /* Message queue */
   gfx_display_reset_textures_list(
         "msg_queue_icon.png",
         gfx_widgets_path,
         &p_dispwidget->msg_queue_icon,
         TEXTURE_FILTER_LINEAR,
         NULL,
         NULL);
   gfx_display_reset_textures_list(
         "msg_queue_icon_outline.png",
         gfx_widgets_path,
         &p_dispwidget->msg_queue_icon_outline,
         TEXTURE_FILTER_LINEAR,
         NULL,
         NULL);
   gfx_display_reset_textures_list(
         "msg_queue_icon_rect.png",
         gfx_widgets_path,
         &p_dispwidget->msg_queue_icon_rect,
         TEXTURE_FILTER_NEAREST,
         NULL,
         NULL);

   p_dispwidget->msg_queue_has_icons = 
      p_dispwidget->msg_queue_icon         && 
      p_dispwidget->msg_queue_icon_outline &&
      p_dispwidget->msg_queue_icon_rect;

   for (i = 0; i < ARRAY_SIZE(widgets); i++)
   {
      const gfx_widget_t* widget = widgets[i];

      if (widget->context_reset)
         widget->context_reset(is_threaded, width, height,
               fullscreen, dir_assets, font_path,
               monochrome_png_path, gfx_widgets_path);
   }

   /* Update scaling/dimensions */
   p_dispwidget->last_video_width  = width;
   p_dispwidget->last_video_height = height;
   p_dispwidget->last_scale_factor = (
         gfx_display_get_driver_id() == MENU_DRIVER_ID_XMB) ?
         gfx_display_get_widget_pixel_scale(p_dispwidget->last_video_width,
               p_dispwidget->last_video_height, fullscreen) :
               gfx_display_get_widget_dpi_scale(
                     p_dispwidget->last_video_width,
                     p_dispwidget->last_video_height,
                     fullscreen);

   gfx_widgets_layout(p_dispwidget,
         is_threaded, dir_assets, font_path);
   video_driver_monitor_reset();
}

static void INLINE gfx_widgets_font_free(gfx_widget_font_data_t *font_data)
{
   if (font_data->font)
      gfx_display_font_free(font_data->font);

   font_data->font        = NULL;
   font_data->usage_count = 0;
}

static void gfx_widgets_context_destroy(dispgfx_widget_t *p_dispwidget)
{
   size_t i;

   for (i = 0; i < ARRAY_SIZE(widgets); i++)
   {
      const gfx_widget_t* widget = widgets[i];

      if (widget->context_destroy)
         widget->context_destroy();
   }

   /* TODO: Dismiss onscreen notifications that have been freed */

   /* Textures */
   for (i = 0; i < MENU_WIDGETS_ICON_LAST; i++)
      video_driver_texture_unload(&p_dispwidget->gfx_widgets_icons_textures[i]);

   video_driver_texture_unload(&p_dispwidget->msg_queue_icon);
   video_driver_texture_unload(&p_dispwidget->msg_queue_icon_outline);
   video_driver_texture_unload(&p_dispwidget->msg_queue_icon_rect);

   p_dispwidget->msg_queue_icon         = 0;
   p_dispwidget->msg_queue_icon_outline = 0;
   p_dispwidget->msg_queue_icon_rect    = 0;

   /* Fonts */
   gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.regular);
   gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.bold);
   gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.msg_queue);
}

static void gfx_widgets_free(dispgfx_widget_t *p_dispwidget)
{
   size_t i;

   p_dispwidget->widgets_inited     = false;

   for (i = 0; i < ARRAY_SIZE(widgets); i++)
   {
      const gfx_widget_t* widget = widgets[i];

      if (widget->free)
         widget->free();
   }

   /* Kill all running animations */
   gfx_animation_kill_by_tag(
         &p_dispwidget->gfx_widgets_generic_tag);

   /* Purge everything from the fifo */
   if (p_dispwidget->msg_queue)
   {
      while (fifo_read_avail(p_dispwidget->msg_queue) > 0)
      {
         menu_widget_msg_t *msg_widget;

         fifo_read(p_dispwidget->msg_queue,
               &msg_widget, sizeof(msg_widget));

         /* Note: gfx_widgets_free() is only called when
          * main_exit() is invoked. At this stage, we cannot
          * guarantee that any task pointers are valid (the
          * task may have been free()'d, but we can't know
          * that here) - so all we can do is unset the task
          * pointer associated with each message
          * > If we don't do this, gfx_widgets_msg_queue_free()
          *   will generate heap-use-after-free errors */
         msg_widget->task_ptr = NULL;

         gfx_widgets_msg_queue_free(p_dispwidget, msg_widget);
         free(msg_widget);
      }

      fifo_free(p_dispwidget->msg_queue);
   }
   p_dispwidget->msg_queue = NULL;

   /* Purge everything from the list */
   SLOCK_LOCK(p_dispwidget->current_msgs_lock);

   p_dispwidget->current_msgs_size = 0;
   for (i = 0; i < ARRAY_SIZE(p_dispwidget->current_msgs); i++)
   {
      menu_widget_msg_t *msg = p_dispwidget->current_msgs[i];
      if (!msg)
         continue;

      /* Note: gfx_widgets_free() is only called when
         * main_exit() is invoked. At this stage, we cannot
         * guarantee that any task pointers are valid (the
         * task may have been free()'d, but we can't know
         * that here) - so all we can do is unset the task
         * pointer associated with each message
         * > If we don't do this, gfx_widgets_msg_queue_free()
         *   will generate heap-use-after-free errors */
      msg->task_ptr = NULL;

      gfx_widgets_msg_queue_free(p_dispwidget, msg);
   }
   SLOCK_UNLOCK(p_dispwidget->current_msgs_lock);

#ifdef HAVE_THREADS
   slock_free(p_dispwidget->current_msgs_lock);
   p_dispwidget->current_msgs_lock = NULL;
#endif

   p_dispwidget->msg_queue_tasks_count = 0;

   /* Font */
   video_coord_array_free(
         &p_dispwidget->gfx_widget_fonts.regular.raster_block.carr);
   video_coord_array_free(
         &p_dispwidget->gfx_widget_fonts.bold.raster_block.carr);
   video_coord_array_free(
         &p_dispwidget->gfx_widget_fonts.msg_queue.raster_block.carr);

   font_driver_bind_block(NULL, NULL);
}

bool gfx_widgets_set_fps_text(
      void *data,
      const char *new_fps_text)
{
   dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data;

   strlcpy(p_dispwidget->gfx_widgets_fps_text,
         new_fps_text, sizeof(p_dispwidget->gfx_widgets_fps_text));

   return true;
}

#ifdef HAVE_TRANSLATE
bool gfx_widgets_ai_service_overlay_load(
      dispgfx_widget_t *p_dispwidget,
      char* buffer, unsigned buffer_len,
      enum image_type_enum image_type)
{
   if (p_dispwidget->ai_service_overlay_state == 0)
   {
      bool res = gfx_display_reset_textures_list_buffer(
               &p_dispwidget->ai_service_overlay_texture, 
               TEXTURE_FILTER_MIPMAP_LINEAR, 
               (void *) buffer, buffer_len, image_type,
               &p_dispwidget->ai_service_overlay_width,
               &p_dispwidget->ai_service_overlay_height);
      if (res)
         p_dispwidget->ai_service_overlay_state = 1;
      return res;
   }
   return true;
}

void gfx_widgets_ai_service_overlay_unload(dispgfx_widget_t *p_dispwidget)
{
   if (p_dispwidget->ai_service_overlay_state == 1)
   {
      video_driver_texture_unload(&p_dispwidget->ai_service_overlay_texture);
      p_dispwidget->ai_service_overlay_texture = 0;
      p_dispwidget->ai_service_overlay_state   = 0;
   }
}
#endif

#ifdef HAVE_MENU
static void gfx_widgets_end_load_content_animation(void *userdata)
{
#if 0
   task_load_content_resume(); /* TODO: Restore that */
#endif
}

void gfx_widgets_cleanup_load_content_animation(void)
{
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)dispwidget_get_ptr();
   p_dispwidget->load_content_animation_running = false;
   if (p_dispwidget->load_content_animation_content_name)
      free(p_dispwidget->load_content_animation_content_name);
   p_dispwidget->load_content_animation_content_name = NULL;
}
#endif

void gfx_widgets_start_load_content_animation(
      const char *content_name, bool remove_extension)
{
#ifdef HAVE_MENU
   /* TODO: finish the animation based on design, correct all timings */
   int i;
   gfx_animation_ctx_entry_t entry;
   gfx_timer_ctx_entry_t timer_entry;
   dispgfx_widget_t *p_dispwidget   = (dispgfx_widget_t*)dispwidget_get_ptr();
   float icon_color[16]             = 
      COLOR_HEX_TO_FLOAT(0x0473C9, 1.0f); /* TODO: random color */
   unsigned timing                  = 0;

   /* Prepare data */
   p_dispwidget->load_content_animation_icon         = 0;

   /* Abort animation if we don't have an icon */
   if (!menu_driver_get_load_content_animation_data(
            &p_dispwidget->load_content_animation_icon,
      &p_dispwidget->load_content_animation_playlist_name) || 
         !p_dispwidget->load_content_animation_icon)
   {
      gfx_widgets_end_load_content_animation(NULL);
      return;
   }

   p_dispwidget->load_content_animation_content_name = strdup(content_name);

   if (remove_extension)
      path_remove_extension(p_dispwidget->load_content_animation_content_name);

   /* Reset animation state */
   p_dispwidget->load_content_animation_icon_size          = p_dispwidget->load_content_animation_icon_size_initial;
   p_dispwidget->load_content_animation_icon_alpha         = 0.0f;
   p_dispwidget->load_content_animation_fade_alpha         = 0.0f;
   p_dispwidget->load_content_animation_final_fade_alpha   = 0.0f;

   memcpy(p_dispwidget->load_content_animation_icon_color, icon_color, sizeof(p_dispwidget->load_content_animation_icon_color));

   /* Setup the animation */
   entry.cb             = NULL;
   entry.easing_enum    = EASING_OUT_QUAD;
   entry.tag            = p_dispwidget->gfx_widgets_generic_tag;
   entry.userdata       = NULL;

   /* Stage one: icon animation */
   /* Position */
   entry.duration       = ANIMATION_LOAD_CONTENT_DURATION;
   entry.subject        = &p_dispwidget->load_content_animation_icon_size;
   entry.target_value   = p_dispwidget->load_content_animation_icon_size_target;

   gfx_animation_push(&entry);

   /* Alpha */
   entry.subject        = &p_dispwidget->load_content_animation_icon_alpha;
   entry.target_value   = 1.0f;

   gfx_animation_push(&entry);
   timing += entry.duration;

   /* Stage two: backdrop + text */
   entry.duration       = ANIMATION_LOAD_CONTENT_DURATION*1.5;
   entry.subject        = &p_dispwidget->load_content_animation_fade_alpha;
   entry.target_value   = 1.0f;

   gfx_animation_push_delayed(timing, &entry);
   timing += entry.duration;

   /* Stage three: wait then color transition */
   timing += ANIMATION_LOAD_CONTENT_DURATION*1.5;

   entry.duration = ANIMATION_LOAD_CONTENT_DURATION*3;

   for (i = 0; i < 16; i++)
   {
      if (i == 3 || i == 7 || i == 11 || i == 15)
         continue;

      entry.subject        = &p_dispwidget->load_content_animation_icon_color[i];
      entry.target_value   = gfx_widgets_pure_white[i];

      gfx_animation_push_delayed(timing, &entry);
   }

   timing += entry.duration;

   /* Stage four: wait then make everything disappear */
   timing += ANIMATION_LOAD_CONTENT_DURATION*2;

   entry.duration       = ANIMATION_LOAD_CONTENT_DURATION*1.5;
   entry.subject        = 
      &p_dispwidget->load_content_animation_final_fade_alpha;
   entry.target_value   = 1.0f;

   gfx_animation_push_delayed(timing, &entry);
   timing += entry.duration;

   /* Setup end */
   timer_entry.cb       = gfx_widgets_end_load_content_animation;
   timer_entry.duration = timing;
   timer_entry.userdata = NULL;

   gfx_timer_start(
         &p_dispwidget->load_content_animation_end_timer,
         &timer_entry);

   /* Draw all the things */
   p_dispwidget->load_content_animation_running = true;
#endif
}