/**
 * menu_animation_ticker_str:
 * @s                        : buffer to write new message line to.
 * @len                      : length of buffer @input.
 * @idx                      : Index. Will be used for ticker logic.
 * @str                      : Input string.
 * @selected                 : Is the item currently selected in the menu?
 *
 * Take the contents of @str and apply a ticker effect to it,
 * and write the results in @s.
 **/
void menu_animation_ticker_str(char *s, size_t len, uint64_t idx,
      const char *str, bool selected)
{
   menu_animation_t *anim = menu_animation_get_ptr();
   size_t           str_len = strlen(str);
   size_t           offset = 0;

   if ((size_t)str_len <= len)
   {
      strlcpy(s, str, len + 1);
      return;
   }

   if (!selected)
   {
      strlcpy(s, str, len + 1 - 3);
      strlcat(s, "...", len + 1);
      return;
   }

   menu_animation_ticker_generic(idx, len, &offset, &str_len);

   strlcpy(s, str - offset, str_len + 1);

   anim->is_active = true;
}
bool menu_animation_ticker(const menu_animation_ctx_ticker_t *ticker)
{
   size_t str_len = utf8len(ticker->str);
   size_t offset  = 0;

   if ((size_t)str_len <= ticker->len)
   {
      utf8cpy(ticker->s,
            PATH_MAX_LENGTH,
            ticker->str,
            ticker->len);
      return true;
   }

   if (!ticker->selected)
   {
      utf8cpy(ticker->s, PATH_MAX_LENGTH, ticker->str, ticker->len - 3);
      strlcat(ticker->s, "...", PATH_MAX_LENGTH);
      return true;
   }

   if (str_len > ticker->len)
      menu_animation_ticker_generic(
            ticker->idx,
            ticker->len,
            &offset,
            &str_len);

   utf8cpy(
         ticker->s,
         PATH_MAX_LENGTH,
         utf8skip(ticker->str, offset),
         str_len);

   animation_is_active = true;

   return true;
}
bool menu_animation_ctl(enum menu_animation_ctl_state state, void *data)
{
   static menu_animation_t anim;
   static retro_time_t cur_time    = 0;
   static retro_time_t old_time    = 0;
   static float delta_time         = 0.0f;
   static bool animation_is_active = false;

   switch (state)
   {
      case MENU_ANIMATION_CTL_DEINIT:
         {
            size_t i;

            for (i = 0; i < anim.size; i++)
            {
               if (anim.list[i].subject)
                  anim.list[i].subject = NULL;
            }

            free(anim.list);

            memset(&anim, 0, sizeof(menu_animation_t));
         }
         cur_time                  = 0;
         old_time                  = 0;
         delta_time                = 0.0f;
         break;
      case MENU_ANIMATION_CTL_IS_ACTIVE:
         return animation_is_active;
      case MENU_ANIMATION_CTL_CLEAR_ACTIVE:
         animation_is_active       = false;
         break;
      case MENU_ANIMATION_CTL_SET_ACTIVE:
         animation_is_active       = true;
         break;
      case MENU_ANIMATION_CTL_DELTA_TIME:
         {
            float *ptr = (float*)data;
            if (!ptr)
               return false;
            *ptr = delta_time;
         }
         break;
      case MENU_ANIMATION_CTL_UPDATE_TIME:
         {
            static retro_time_t last_clock_update = 0;
            settings_t *settings     = config_get_ptr();

            cur_time                 = cpu_features_get_time_usec();
            delta_time               = cur_time - old_time;

            if (delta_time >= IDEAL_DELTA_TIME* 4)
               delta_time = IDEAL_DELTA_TIME * 4;
            if (delta_time <= IDEAL_DELTA_TIME / 4)
               delta_time = IDEAL_DELTA_TIME / 4;
            old_time      = cur_time;

            if (((cur_time - last_clock_update) > 1000000) 
                  && settings->menu.timedate_enable)
            {
               animation_is_active   = true;
               last_clock_update     = cur_time;
            }
         }
         break;
      case MENU_ANIMATION_CTL_UPDATE:
         {
            unsigned i;
            unsigned active_tweens = 0;
            float *dt = (float*)data;

            if (!dt)
               return false;

            for(i = 0; i < anim.size; i++)
               menu_animation_iterate(&anim, i, *dt, &active_tweens);

            if (!active_tweens)
            {
               anim.size       = 0;
               anim.first_dead = 0;
               return false;
            }

            animation_is_active = true;
         }
         break;
      case MENU_ANIMATION_CTL_KILL_BY_TAG:
         {
            unsigned i;
            menu_animation_ctx_tag_t *tag = (menu_animation_ctx_tag_t*)data;

            if (!tag || tag->id == -1)
               return false;

            for (i = 0; i < anim.size; ++i)
            {
               if (anim.list[i].tag != tag->id)
                  continue;

               anim.list[i].alive   = false;
               anim.list[i].subject = NULL;

               if (i < anim.first_dead)
                  anim.first_dead = i;
            }
         }
         break;
      case MENU_ANIMATION_CTL_KILL_BY_SUBJECT:
         {
            unsigned i, j,  killed = 0;
            menu_animation_ctx_subject_t *subject = 
               (menu_animation_ctx_subject_t*)data;
            float            **sub = (float**)subject->data;

            for (i = 0; i < anim.size; ++i)
            {
               if (!anim.list[i].alive)
                  continue;

               for (j = 0; j < subject->count; ++j)
               {
                  if (anim.list[i].subject != sub[j])
                     continue;

                  anim.list[i].alive   = false;
                  anim.list[i].subject = NULL;

                  if (i < anim.first_dead)
                     anim.first_dead = i;

                  killed++;
                  break;
               }
            }
         }
         break;
      case MENU_ANIMATION_CTL_TICKER:
         {
            menu_animation_ctx_ticker_t *ticker = (menu_animation_ctx_ticker_t*)
               data;
            size_t           str_len = utf8len(ticker->str);
            size_t           offset  = 0;

            if ((size_t)str_len <= ticker->len)
            {
               utf8cpy(ticker->s,
                     PATH_MAX_LENGTH,
                     ticker->str,
                     ticker->len);
               return true;
            }

            if (!ticker->selected)
            {
               utf8cpy(ticker->s, PATH_MAX_LENGTH, ticker->str, ticker->len - 3);
               strlcat(ticker->s, "...", PATH_MAX_LENGTH);
               return true;
            }

            menu_animation_ticker_generic(
                  ticker->idx,
                  ticker->len,
                  &offset,
                  &str_len);

            utf8cpy(
                  ticker->s,
                  PATH_MAX_LENGTH,
                  utf8skip(ticker->str, offset),
                  str_len);

            animation_is_active = true;
         }
         break;
      case MENU_ANIMATION_CTL_IDEAL_DELTA_TIME_GET:
         {
            menu_animation_ctx_delta_t *delta = 
               (menu_animation_ctx_delta_t*)data;
            if (!delta)
               return false;
            delta->ideal = delta->current / IDEAL_DELTA_TIME;
         }
         break;
      case MENU_ANIMATION_CTL_PUSH:
         return menu_animation_push(&anim, data);
      case MENU_ANIMATION_CTL_NONE:
      default:
         break;
   }

   return true;
}