/** PSP helper library ***************************************/
/**                                                         **/
/**                           ui.c                          **/
/**                                                         **/
/** This file contains a simple GUI rendering library       **/
/**                                                         **/
/** Copyright (C) Akop Karapetyan 2007                      **/
/**     You are not allowed to distribute this software     **/
/**     commercially. Please, notify me, if you make any    **/
/**     changes to this file.                               **/
/*************************************************************/
#include <malloc.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <pspkernel.h>
#include <psprtc.h>

#include "psp.h"
#include "fileio.h"
#include "ctrl.h"
#include "ui.h"

#define POS_LEFT   8
#define POS_TOP    28
#define POS_RIGHT  (SCR_WIDTH-8)
#define POS_BOTTOM 236

#define CLR_SCROLLBAR  PSP_VIDEO_GRAY
#define CLR_SHORTCUTS  PSP_VIDEO_WHITE
#define CLR_SELECTED   PSP_VIDEO_GREEN
#define CLR_TEXT       PSP_VIDEO_GRAY
#define CLR_FILE       PSP_VIDEO_GRAY
#define CLR_DIR        PSP_VIDEO_YELLOW
#define CLR_PATH       PSP_VIDEO_WHITE
#define CLR_DLG_BORDER PSP_VIDEO_GRAY
#define CLR_DLG_BG     PSP_VIDEO_DARKGRAY
#define CLR_ARROWS     PSP_VIDEO_YELLOW
#define CLR_MENU_OPTION_BG RGBA(0, 0, 0, 0xBB)
#define CLR_MENU_OPTION_SEL_BG PSP_VIDEO_BLACK

#define GAL_ICONS_PER_ROW 5
#define GAL_ICON_MARGIN_W 8

#define MENU_ITEM_MARGIN  20

#define SCROLLBAR_WIDTH   10

#define  CONTROL_BUTTON_MASK \
  (PSP_CTRL_CIRCLE | PSP_CTRL_TRIANGLE | PSP_CTRL_CROSS | PSP_CTRL_SQUARE | \
   PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER)

unsigned int pspUiFileBrowser(PspImage *backdrop,
  const char *path, const char **filter, char **buffer,
  unsigned int ok_btn_mask, unsigned int cncl_btn_mask,
  void(*CustomDraw)())
{
  PspMenu *menu;
  PspFile *file;
  PspFileList *list;
  PspMenuItem *item, *sel, *top;
  SceCtrlData pad, lpad;

  char instr[128];
  char *cur_path = pspFileIoGetParentDirectory(path);
  const char *cur_file = pspFileIoGetFilename(path);

  int lnindex, lnoffset, lnmax, lnhalf;
  int sby, sbh, i, j, h, w, fh = pspFontGetLineHeight(UiMetric.Font);
  int sx = POS_LEFT, sy = POS_TOP + fh, dx = POS_RIGHT, dy = POS_BOTTOM;
  int hasparent;

  menu = pspMenuCreate();
  w = dx - sx - SCROLLBAR_WIDTH;
  h = dy - sy;

  memset(&pad, 0, sizeof(SceCtrlData));
  memset(&lpad, 0, sizeof(SceCtrlData));

  /* Begin browsing (outer) loop */
  while (!ExitPSP)
  {
    sel = top = NULL;
    pspMenuClear(menu);

    /* Load list of files for the selected path */
    if ((list = pspFileIoGetFileList(cur_path, filter)))
    {
      /* Check for a parent path, prepend .. if necessary */
      if ((hasparent=!pspFileIoIsRootDirectory(cur_path)))
        pspMenuAppendItem(menu, "..", (void*)PSP_FILEIO_DIR);

      /* Add a menu item for each file */
      for (file = list->First; file; file = file->Next)
      {
        /* Skip files that begin with '.' */
        if (file->Name && file->Name[0] == '.')
          continue;

        item = pspMenuAppendItem(menu, file->Name, (void*)file->Attrs);
        if (cur_file && strcmp(file->Name, cur_file) == 0)
          sel = item;
      }

      cur_file = NULL;

      /* Destroy the file list */
      pspFileIoDestroyFileList(list);
    }
    else
    {
      /* Check for a parent path, prepend .. if necessary */
      if ((hasparent=!pspFileIoIsRootDirectory(cur_path)))
        pspMenuAppendItem(menu, "..", (void*)PSP_FILEIO_DIR);
    }

    /* Initialize variables */
    lnmax = (dy - sy) / fh;
    lnhalf = lnmax >> 1;
    sbh = (menu->Count > lnmax) ? (int)((float)h * ((float)lnmax / (float)menu->Count)) : 0;

    lnindex=0;
    lnoffset=0;

    if (!sel) 
    { 
      /* Select the first file/dir in the directory */
      if (menu->First && menu->First->Next)
        sel=menu->First->Next;
      else if (menu->First)
        sel=menu->First;
    }

    /* Compute index and offset of selected file */
    if (sel)
    {
      top=menu->First;
      for (item=menu->First; item != sel; item=item->Next)
        if (lnindex+1 >= lnmax) { lnoffset++; top=top->Next; } else lnindex++;
    }

    pspVideoWaitVSync();

    /* Begin navigation (inner) loop */
    while (!ExitPSP)
    {
      if (!pspCtrlPollControls(&pad))
        continue;

      /* Check the directional buttons */
      if (sel)
      {
        if ((pad.Buttons & PSP_CTRL_DOWN || pad.Ly >= 224) && sel->Next)
        {
          if (lnindex+1 >= lnmax) { lnoffset++; top=top->Next; } 
          else lnindex++;
          sel=sel->Next;
        }
        else if ((pad.Buttons & PSP_CTRL_UP || pad.Ly < 32) && sel->Prev)
        {
          if (lnindex-1 < 0) { lnoffset--; top=top->Prev; }
          else lnindex--;
          sel = sel->Prev;
        }
        else if (pad.Buttons & PSP_CTRL_LEFT)
        {
          for (i=0; sel->Prev && i < lnhalf; i++)
          {
            if (lnindex-1 < 0) { lnoffset--; top=top->Prev; }
            else lnindex--;
            sel=sel->Prev;
          }
        }
        else if (pad.Buttons & PSP_CTRL_RIGHT)
        {
          for (i=0; sel->Next && i < lnhalf; i++)
          {
            if (lnindex+1 >= lnmax) { lnoffset++; top=top->Next; }
            else lnindex++;
            sel=sel->Next;
          }
        }

        /* File/dir selection */
        if (pad.Buttons & ok_btn_mask)
        {
          if (((unsigned int)sel->Userdata & PSP_FILEIO_DIR))
          {
            /* Selected a directory, descend */
            pspFileIoEnterDirectory(&cur_path, sel->Caption);
            break;
          }
          else
          {
            /* Selected a file */
            if (*buffer) free(*buffer);
            *buffer=malloc((strlen(cur_path) + strlen(sel->Caption) + 1) * sizeof(char));
            sprintf(*buffer, "%s%s", cur_path, sel->Caption);
            goto exit_browser;
          }
        }
      }

      /* Check the "action" buttons */
      if (pad.Buttons & PSP_CTRL_TRIANGLE)
      {
        if (!pspFileIoIsRootDirectory(cur_path))
        {
          pspFileIoEnterDirectory(&cur_path, "..");
          break;
        }
      }
      else if (pad.Buttons & cncl_btn_mask)
        goto exit_browser;

      /* Clear screen */
      if (backdrop) pspVideoPutImage(backdrop, 0, 0, backdrop->Width, backdrop->Height);
      else pspVideoClearScreen();

      /* Draw current path */
      j = sx;
      j += pspVideoPrint(UiMetric.Font, j, POS_TOP, "\271 ", CLR_PATH);
      pspVideoPrint(UiMetric.Font, j, POS_TOP, cur_path, CLR_PATH);

      /* Draw instructions */
      sprintf(instr, "%s%s\026\241\020 Cancel", 
        (sel) ? ((unsigned int)sel->Userdata & PSP_FILEIO_DIR) ? "\026\242\020 Enter directory\t" : "\026\242\020 Open\t" : "",
        (hasparent) ? "\026\243\020 Parent directory\t" : "");
      pspVideoPrint(UiMetric.Font, 0, SCR_HEIGHT - fh, instr, CLR_SHORTCUTS);

      /* Draw scrollbar */
      if (sbh > 0)
      {
        sby = sy + (int)((float)(h - sbh) * ((float)(lnoffset + lnindex) / (float)menu->Count));
        pspVideoFillRect(dx - SCROLLBAR_WIDTH, sy, dx, dy, 0x44ffffff);
        pspVideoFillRect(dx - SCROLLBAR_WIDTH, sby, dx, sby + sbh, CLR_SCROLLBAR);
      }

      /* Render the files */
      for (item = top, i = 0, j = sy; item && i < lnmax; item = item->Next, j += fh, i++)
      {
        if (item == sel)
          pspVideoFillRect(sx, j, sx + w, j + fh, 0x55000000);

        pspVideoPrintClipped(UiMetric.Font, sx + 10, j, item->Caption, w - 10, "...", 
          (item == sel) ? CLR_SELECTED :
            ((unsigned int)item->Userdata & PSP_FILEIO_DIR) ? CLR_DIR : CLR_FILE);
      }

      /* Perform any custom drawing */
      if (CustomDraw)
        CustomDraw();

      /* Swap buffers */
      sceKernelDcacheWritebackAll();
      pspVideoWaitVSync();
      pspVideoSwapBuffers();

      lpad = pad;
    }
  }

exit_browser:
  pspMenuDestroy(menu);
  free(cur_path);

  return (ExitPSP) ? 0 : pad.Buttons;
}

unsigned int pspUiMenu(PspImage *backdrop,
  PspMenu *menu, PspMenuItem **sel,
  unsigned int ok_btn_mask, unsigned int cncl_btn_mask,
  char*(*ShortcutHandler)(PspMenuItem*, unsigned int, unsigned int),
  void(*CustomDraw)(),
  int (*ItemChangedCallback)(const PspMenuItem*, const PspMenuOption*))
{
  PspMenuItem *item, *top;
  SceCtrlData pad, lpad;
  const PspMenuOption *temp_option;

  int lnindex, lnoffset, lnmax, lnhalf;
  int sby, sbh, i, j, k, h, w, fh = pspFontGetLineHeight(UiMetric.Font);
  int sx = POS_LEFT, sy = POS_TOP, dx = POS_RIGHT, dy = POS_BOTTOM;
  int max_item_w = 0, item_w;
  int option_mode, max_option_w = 0;
  int arrow_w = pspFontGetTextWidth(UiMetric.Font, "\272");
  int anim_frame = 0, anim_incr = 1;

  w = dx - sx - SCROLLBAR_WIDTH;
  h = dy - sy;

  memset(&pad, 0, sizeof(SceCtrlData));
  memset(&lpad, 0, sizeof(SceCtrlData));

  /* Determine width of the longest caption */
  for (item=menu->First; item; item=item->Next)
  {
    if (item->Caption)
    {
      item_w=pspFontGetTextWidth(UiMetric.Font, item->Caption);
      if (item_w > max_item_w) max_item_w = item_w;
    }
  }

  top = NULL;

  /* Initialize variables */
  lnmax = (dy - sy) / fh;
  lnhalf = lnmax >> 1;
  sbh = (menu->Count > lnmax) ? (int)((float)h * ((float)lnmax / (float)menu->Count)) : 0;

  lnindex=0;
  lnoffset=0;
  top=0;
  option_mode = 0;
  temp_option = NULL;

  /* Compute index and offset of selected file */
  if (!*sel)
  {
    top=*sel=menu->First;
  }
  else
  {
    top=menu->First;
    for (item=menu->First; item != *sel; item=item->Next)
      if (lnindex+1 >= lnmax) { lnoffset++; top=top->Next; } else lnindex++;
  }

  pspVideoWaitVSync();
  PspMenuItem *last;
  
  /* Begin navigation loop */
  while (!ExitPSP)
  {
    if (!pspCtrlPollControls(&pad))
      continue;

    anim_frame += anim_incr;
    if (anim_frame > 2 || anim_frame < 0) 
      anim_incr *= -1;

    /* Check the directional buttons */
    if (*sel)
    {
      if (pad.Buttons & PSP_CTRL_DOWN || pad.Ly >= 224)
      {
        if (option_mode)
        {
          if (temp_option->Next)
            temp_option = temp_option->Next;
        }
        else
        {
          if ((*sel)->Next)
          {
            last=*sel;
            for (;;)
            {
              if (lnindex+1 >= lnmax) { lnoffset++; top=top->Next; } 
              else lnindex++;
              *sel=(*sel)->Next;
              if (!*sel) { *sel=last; break; }
              if ((*sel)->Caption) break;
            }
          }
        }
      }
      else if (pad.Buttons & PSP_CTRL_UP || pad.Ly < 32)
      {
        if (option_mode)
        {
          if (temp_option->Prev)
            temp_option = temp_option->Prev;
        }
        else
        {
          if ((*sel)->Prev)
          {
            last=*sel;
            for (;;)
            {
              if (lnindex-1 < 0) { lnoffset--; top=top->Prev; }
              else lnindex--;
              *sel=(*sel)->Prev;
              if (!*sel) { *sel=last; break; }
              if ((*sel)->Caption) break;
            }
          }
        }
      }

      if (option_mode)
      {
        if (pad.Buttons & PSP_CTRL_RIGHT)
        {
          option_mode = 0;

          /* If the callback function refuses the change, do not perform it */
          if (!ItemChangedCallback || ItemChangedCallback(*sel, temp_option))
            (*sel)->Selected = temp_option;
        }
        else if (pad.Buttons & PSP_CTRL_LEFT)
        {
          option_mode = 0;
        }
      }
      else
      {
        if (pad.Buttons & PSP_CTRL_RIGHT)
        {
          if ((*sel)->Options && (*sel)->Options->Next)
          {
            option_mode = 1;

            /* Find the longest option caption */
            max_option_w = 0;
            int width;
            PspMenuOption *option;
            for (option = (*sel)->Options; option->Next; option = option->Next)
              if (option->Text && (width = pspFontGetTextWidth(UiMetric.Font, option->Text)) > max_option_w) 
                max_option_w = width;

            if ((*sel)->Selected)
              temp_option = (*sel)->Selected;
            else
              temp_option = (*sel)->Options;
          }
        }

        if (pad.Buttons & ok_btn_mask)
          break;
      }
    }

    if (!option_mode && (pad.Buttons & cncl_btn_mask))
      break;

    /* Clear screen */
    if (backdrop) pspVideoPutImage(backdrop, 0, 0, backdrop->Width, backdrop->Height);
    else pspVideoClearScreen();

    /* Draw instructions */
    if (*sel)
    {
      const char *dirs = NULL;

      if (option_mode)
        dirs = "\026\245\246\020 Select\t\026\247\020 Cancel\t\026\250\020 Confirm";
      else if (ShortcutHandler)
        dirs = ShortcutHandler(*sel, ok_btn_mask, cncl_btn_mask);

      if (dirs) pspVideoPrint(UiMetric.Font, 0, SCR_HEIGHT - fh, dirs, CLR_SHORTCUTS);
    }

    /* Render the menu items */
    for (item = top, i = 0, j = sy; item && i < lnmax; item = item->Next, j += fh, i++)
    {
      if (item->Caption)
      {
        if (item == *sel)
          pspVideoFillRect(sx, j, sx + w, j + fh, 0x55000000);

        pspVideoPrint(UiMetric.Font, sx + 10, j, item->Caption, (item==*sel) ? CLR_SELECTED : CLR_TEXT);

        /* Render selected option */
        if (item->Selected)
        {
          k = sx + max_item_w + MENU_ITEM_MARGIN + 10;
          k += pspVideoPrint(UiMetric.Font, k, j, item->Selected->Text, (item==*sel) ? CLR_SELECTED : CLR_TEXT);

          if (!option_mode && item == *sel)
          {
            if ((*sel)->Options && (*sel)->Options->Next)
              pspVideoPrint(UiMetric.Font, k + anim_frame, j, " >", PSP_VIDEO_YELLOW);
          }
        }
      }
    }

    /* Render menu options */
    if (option_mode)
    {
      /* Render selected item + previous items */
      i = sy + lnindex * fh;
      k = sx + max_item_w + MENU_ITEM_MARGIN + 10;
      int arrow_x = k - MENU_ITEM_MARGIN + (MENU_ITEM_MARGIN / 2 - arrow_w / 2);
      const PspMenuOption *option;

      for (option = temp_option; option && i >= sy; option = option->Prev, i -= fh)
      {
        pspVideoFillRect(k - MENU_ITEM_MARGIN, i, k + max_option_w + MENU_ITEM_MARGIN, i + fh, 
          (option == temp_option) ? CLR_MENU_OPTION_SEL_BG : CLR_MENU_OPTION_BG);
        pspVideoPrint(UiMetric.Font, k, i, option->Text, 
          (option == temp_option) ? CLR_SELECTED : CLR_TEXT);
      }
      pspVideoFillRect(k - MENU_ITEM_MARGIN, i + fh - 8, k + max_option_w + MENU_ITEM_MARGIN, i + fh, CLR_MENU_OPTION_BG);

      /* Up arrow */
      if (option)
        pspVideoPrint(UiMetric.Font, arrow_x, i + fh + anim_frame, "\272", PSP_VIDEO_YELLOW);

      /* Render following items */
      i = sy + (lnindex  + 1) * fh;
      for (option = temp_option->Next; option && i < dy; option = option->Next, i += fh)
      {
        pspVideoFillRect(k - MENU_ITEM_MARGIN, i, k + max_option_w + MENU_ITEM_MARGIN, i + fh, CLR_MENU_OPTION_BG);
        pspVideoPrint(UiMetric.Font, k, i, option->Text, CLR_TEXT);
      }
      pspVideoFillRect(k - MENU_ITEM_MARGIN, i, k + max_option_w + MENU_ITEM_MARGIN, i + 8, CLR_MENU_OPTION_BG);

      /* Down arrow */
      if (option)
        pspVideoPrint(UiMetric.Font, arrow_x, i - fh - anim_frame, "\273", PSP_VIDEO_YELLOW);
    }

    /* Perform any custom drawing */
    if (CustomDraw)
      CustomDraw();

    /* Draw scrollbar */
    if (sbh > 0)
    {
      sby = sy + (int)((float)(h - sbh) * ((float)(lnoffset + lnindex) / (float)menu->Count));
      pspVideoFillRect(dx - SCROLLBAR_WIDTH, sy, dx, dy, 0x44ffffff);
      pspVideoFillRect(dx - SCROLLBAR_WIDTH, sby, dx, sby + sbh, CLR_SCROLLBAR);
    }

    /* Swap buffers */
    sceKernelDcacheWritebackAll();
    pspVideoWaitVSync();
    pspVideoSwapBuffers();

    lpad = pad;
  }

  return (ExitPSP) ? 0 : pad.Buttons;
}

unsigned int pspUiGallery(PspImage *backdrop,
  PspMenu *menu, PspMenuItem **sel,
  unsigned int ok_btn_mask, unsigned int cncl_btn_mask,
  char*(*ShortcutHandler)(PspMenuItem*, unsigned int, unsigned int),
  void(*CustomDraw)())
{
  PspMenuItem *item, *top;
  SceCtrlData pad;

  int sx, sy, dx, dy, x,
    orig_w=272, orig_h=228, // defaults
    fh, c, i, j,
    sbh, sby,
    w, h, 
    icon_w, icon_h, 
    grid_w, grid_h,
    icon_idx, icon_off,
    rows, vis_v, vis_s,
    icons;

  /* Find first icon and save its width/height */
  for (item=menu->First; item; item=item->Next)
  {
    if (item->Icon)
    {
      orig_w=((PspImage*)item->Icon)->Width;
      orig_h=((PspImage*)item->Icon)->Height;
      break;
    }
  }

  fh=pspFontGetLineHeight(UiMetric.Font);
  sx=POS_LEFT;
  sy=POS_TOP;
  dx=POS_RIGHT;
  dy=POS_BOTTOM;
  w=(dx-sx)-SCROLLBAR_WIDTH; // visible width
  h=dy-sy; // visible height
  icon_w=(w-GAL_ICON_MARGIN_W*(GAL_ICONS_PER_ROW-1))/GAL_ICONS_PER_ROW; // icon width
  icon_h=(int)((float)icon_w/((float)orig_w/(float)orig_h)); // icon height
  grid_w=icon_w+GAL_ICON_MARGIN_W; // width of the grid
  grid_h=icon_h+(fh*2); // half-space for margin + 1 line of text 
  icons=menu->Count; // number of icons total
  rows=ceil((float)icons/(float)GAL_ICONS_PER_ROW); // number of rows total
  vis_v=h/grid_h; // number of rows visible at any time
  vis_s=GAL_ICONS_PER_ROW*vis_v; // max. number of icons visible on screen at any time

  icon_idx=0;
  icon_off=0;
  top=menu->First;

  if (!*sel)
  {
    /* Select the first icon */
    *sel=menu->First;
  }
  else
  {
    /* Find the selected icon */
    for (item=menu->First; item; item=item->Next)
    {
      if (item==*sel) break;

      if (++icon_idx >= vis_s)
      {
        icon_idx=0;
        icon_off+=vis_s;
        top=item;
      }
    }
    
    if (item!=*sel)
    {
      /* Icon not found; reset to first icon */
      *sel=menu->First;
      top=menu->First;
      icon_idx=0;
      icon_off=0;
    }
  }

  /* Compute height of scrollbar */
  sbh=((float)vis_v/(float)(rows+(rows%vis_v)))*(float)h;

  pspVideoWaitVSync();

  int glow = 2, glow_dir = 1;

  /* Begin navigation loop */
  while (!ExitPSP)
  {
    if (!pspCtrlPollControls(&pad))
      continue;

    /* Check the directional buttons */
    if (*sel)
    {
      if (pad.Buttons & PSP_CTRL_RIGHT && (*sel)->Next)
      {
        *sel=(*sel)->Next;
        if (++icon_idx >= vis_s)
        {
          icon_idx=0;
          icon_off+=vis_s;
          top=*sel;
        }
      }
      else if (pad.Buttons & PSP_CTRL_LEFT && (*sel)->Prev)
      {
        *sel=(*sel)->Prev;
        if (--icon_idx < 0)
        {
          icon_idx=vis_s-1;
          icon_off-=vis_s;
          for (i=0; i < vis_s && top; i++) top=top->Prev;
        }
      }
      else if (pad.Buttons & PSP_CTRL_DOWN)
      {
        for (i=0; (*sel)->Next && i < GAL_ICONS_PER_ROW; i++)
        {
          *sel=(*sel)->Next;
          if (++icon_idx >= vis_s)
          {
            icon_idx=0;
            icon_off+=vis_s;
            top=*sel;
          }
        }
      }
      else if (pad.Buttons & PSP_CTRL_UP)
      {
        for (i=0; (*sel)->Prev && i < GAL_ICONS_PER_ROW; i++)
        {
          *sel=(*sel)->Prev;
          if (--icon_idx < 0)
          {
            icon_idx=vis_s-1;
            icon_off-=vis_s;
            for (j=0; j < vis_s && top; j++) top=top->Prev;
          }
        }
      }

      if (pad.Buttons & ok_btn_mask)
        break;
    }

    /* Check the "action" buttons */
    if (pad.Buttons & cncl_btn_mask)
      break;

    /* Clear screen */
    if (backdrop) pspVideoPutImage(backdrop, 0, 0, backdrop->Width, backdrop->Height);
    else pspVideoClearScreen();

    /* Draw scrollbar */
    if (sbh < h)
    {
      sby=sy+(((float)icon_off/(float)GAL_ICONS_PER_ROW)/(float)(rows+(rows%vis_v)))*(float)h;
      pspVideoFillRect(dx-SCROLLBAR_WIDTH, sby, dx, sby+sbh, CLR_SCROLLBAR);
    }

    /* Draw instructions */
    if (*sel && ShortcutHandler)
    {
      const char *dirs = ShortcutHandler(*sel, ok_btn_mask, cncl_btn_mask);
      if (dirs) pspVideoPrint(UiMetric.Font, 0, SCR_HEIGHT - fh, dirs, CLR_SHORTCUTS);
    }

    /* Render the menu items */
    for (i=sy, item=top; item && i+grid_h < dy; i+=grid_h)
    {
      for (j=sx, c=0; item && c < GAL_ICONS_PER_ROW; j+=grid_w, c++, item=item->Next)
      {
        if (item->Icon)
        {
          if (item == *sel)
            pspVideoPutImage((PspImage*)item->Icon, j - 4, i - 4, icon_w + 8, icon_h + 8);
          else
            pspVideoPutImage((PspImage*)item->Icon, j, i, icon_w, icon_h);
        }

        if (item == *sel)
        {
          pspVideoDrawRect(j - 4, i - 4, j+icon_w + 4, i+icon_h + 4, CLR_TEXT);
          glow += glow_dir;
          if (glow >= 5 || glow <= 2) glow_dir *= -1;
          pspVideoGlowRect(j - 4, i - 4, j+icon_w + 4, i+icon_h + 4, PSP_VIDEO_WHITE, glow);
        }
        else 
        {
          pspVideoShadowRect(j, i, j + icon_w, i + icon_h, PSP_VIDEO_BLACK, 3);
          pspVideoDrawRect(j, i, j+icon_w, i+icon_h, CLR_TEXT);
        }

        if (item->Caption)
        {
          x=j+icon_w/2-pspFontGetTextWidth(UiMetric.Font, item->Caption)/2;
          pspVideoPrint(UiMetric.Font, x, i+icon_h+(fh/2), item->Caption, (item==*sel) ? CLR_SELECTED : CLR_TEXT);
        }
      }
    }

    /* Perform any custom drawing */
    if (CustomDraw)
      CustomDraw();

    /* Swap buffers */
    sceKernelDcacheWritebackAll();
    pspVideoWaitVSync();
    pspVideoSwapBuffers();
  }

  return (ExitPSP) ? 0 : pad.Buttons;
}

void pspUiAlert(const char *message)
{
  int sx, sy, dx, dy, fh, mw, cw, w, h;
  static const char *confirm = "\026\242\020 Close";

  mw = pspFontGetTextWidth(UiMetric.Font, message);
  cw = pspFontGetTextWidth(UiMetric.Font, confirm);
  fh = pspFontGetLineHeight(UiMetric.Font);

  w = ((mw > cw) ? mw : cw) + 50;
  h = fh * 4;
  sx = SCR_WIDTH / 2 - w / 2;
  sy = SCR_HEIGHT / 2 - h / 2;
  dx = sx + w;
  dy = sy + h;

  pspVideoFillRect(0, 0, SCR_WIDTH, SCR_HEIGHT, 0x88000000);

  pspVideoFillRect(sx, sy, dx, dy, CLR_DLG_BG);
  pspVideoDrawRect(sx + 1, sy + 1, dx - 1, dy - 1, CLR_DLG_BORDER);
  pspVideoPrint(UiMetric.Font, SCR_WIDTH / 2 - mw / 2, sy + fh * 0.5, message, CLR_TEXT);
  pspVideoPrint(UiMetric.Font, SCR_WIDTH / 2 - cw / 2, sy + fh * 2.5, confirm, CLR_TEXT);

  /* Swap buffers */
  sceKernelDcacheWritebackAll();
  pspVideoWaitVSync();
  pspVideoSwapBuffers();

  SceCtrlData pad;

  /* Loop until X or O is pressed */
  while (!ExitPSP)
  {
    if (!pspCtrlPollControls(&pad))
      continue;

    if (pad.Buttons & PSP_CTRL_CROSS || pad.Buttons & PSP_CTRL_CIRCLE)
      break;
  }
}

int pspUiConfirm(const char *message)
{
  int sx, sy, dx, dy, fh, mw, cw, w, h;
  static const char *confirm = "\026\242\020 Confirm\t\026\241\020 Cancel";

  mw = pspFontGetTextWidth(UiMetric.Font, message);
  cw = pspFontGetTextWidth(UiMetric.Font, confirm);
  fh = pspFontGetLineHeight(UiMetric.Font);

  w = ((mw > cw) ? mw : cw) + 50;
  h = fh * 4;
  sx = SCR_WIDTH / 2 - w / 2;
  sy = SCR_HEIGHT / 2 - h / 2;
  dx = sx + w;
  dy = sy + h;

  pspVideoFillRect(0, 0, SCR_WIDTH, SCR_HEIGHT, 0x88000000);

  pspVideoFillRect(sx, sy, dx, dy, CLR_DLG_BG);
  pspVideoDrawRect(sx + 1, sy + 1, dx - 2, dy - 2, CLR_DLG_BORDER);
  pspVideoPrint(UiMetric.Font, SCR_WIDTH / 2 - mw / 2, sy + fh * 0.5, message, CLR_TEXT);
  pspVideoPrint(UiMetric.Font, SCR_WIDTH / 2 - cw / 2, sy + fh * 2.5, confirm, CLR_TEXT);

  /* Swap buffers */
  sceKernelDcacheWritebackAll();
  pspVideoWaitVSync();
  pspVideoSwapBuffers();

  SceCtrlData pad;

  /* Loop until X or O is pressed */
  while (!ExitPSP)
  {
    if (!pspCtrlPollControls(&pad))
      continue;

    if (pad.Buttons & PSP_CTRL_CROSS || pad.Buttons & PSP_CTRL_CIRCLE)
      break;
  }

  return pad.Buttons & PSP_CTRL_CIRCLE;
}

void pspUiFlashMessage(const char *message)
{
  int sx, sy, dx, dy, fh, mw, w, h;

  mw = pspFontGetTextWidth(UiMetric.Font, message);
  fh = pspFontGetLineHeight(UiMetric.Font);

  w = mw + 50;
  h = fh * 3;
  sx = SCR_WIDTH / 2 - w / 2;
  sy = SCR_HEIGHT / 2 - h / 2;
  dx = sx + w;
  dy = sy + h;

  pspVideoFillRect(0, 0, SCR_WIDTH, SCR_HEIGHT, 0x88000000);

  pspVideoFillRect(sx, sy, dx, dy, CLR_DLG_BG);
  pspVideoDrawRect(sx + 1, sy + 1, dx - 1, dy - 1, CLR_DLG_BORDER);
  pspVideoPrint(UiMetric.Font, SCR_WIDTH / 2 - mw / 2, sy + fh, message, CLR_TEXT);

  /* Swap buffers */
  sceKernelDcacheWritebackAll();
  pspVideoWaitVSync();
  pspVideoSwapBuffers();
}


