/*--------------------------------------------------------------------------
  Project:  Manual 2D Library - m2d
  File:     m2d_manualTocPanel.cpp

  Copyright (C)2007-2009 Nintendo. All Rights Reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law. They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Revision: 2014 $
 *-------------------------------------------------------------------------*/

#include <ntmv/m2d/textCanvasContext.h>

#include <ntmv/m2d/manualTocPanel.h>
#include <ntmv/m2d/manualTableOfContents.h>
#include <ntmv/m2d/graphicDefines.h>
#include <ntmv/m2d/uiResDefines.h>
#include <ntmv/m2d/Rect.h>
#include <ntmv/m2d/inlines.h>
#include <ntmv/m2d/touchPanelData.h>
#include <ntmv/m2d/objectConstructor.h>
#include <ntmv/m2d/objButton.h>
#include <ntmv/m2d/uiStatic.h>
#include <ntmv/m2d/scrollButton.h>
#include <ntmv/m2d/soundManager.h>
#include <ntmv/m2d/nlv_skin_NANR_LBLDEFS.h>
#include <ntmv/m2d/sound_data.sadl>
#include <cwchar>

namespace ntmv
{
namespace m2d
{

namespace
{

const s16   HEADER_BUTTON_POS_X         = -30;  // X-coordinate of the header button when the header is displayed
const s16   CHAPTER_BUTTON_POS_X        =  31;  // X-coordinate of the section button when the section is displayed

const s16   HEADER_BUTTON_WHOLE_POS_X   = -102; // X-coordinate of the header button when the focus is on the entire display
const s16   CHAPTER_BUTTON_WHOLE_POS_X  =   96; // X-coordinate of the section button when the focus is on the entire display

const s16   HEADER_BUTTON_BASE_POS_Y    = -36;  // Base y-coordinate for the header button

const s16   CHAPTER_BUTTON_BASE_POS_Y   = -59;  // Base y-coordinate for the section button

const s16   ITEM_BUTTON_HEIGHT          =  20;  // Height of the header and section buttons

const s16   ITEM_BUTTON_LINEHEIGHT      = ITEM_BUTTON_HEIGHT + 3;

const int   ITEM_TEXT_1D_CWIDTH         =  32;   // Width (in characters) of a character in the header and section text
const int   ITEM_TEXT_1D_CHEIGHT        =   3;

const int   HEADER_NUM_CWIDTH           =   4;  // Width (in characters) of a number in the header text

const int   HEADER_TEXT_CWIDTH          =  20;  // Width (in characters) of the header text

const int   CHAPTER_TEXT_CWIDTH         =  24;  // Width (in characters) of the section text

const int   ITEM_TEXT_CHEIGHT           =   4;  // Height (in characters) of the header and section text

// Distance between the centers of the header and section buttons
const int   ITEM_BUTTON_CENTER_DISTANCE =   (CHAPTER_BUTTON_WHOLE_POS_X - CHAPTER_BUTTON_POS_X)
                                          - (HEADER_BUTTON_WHOLE_POS_X  - HEADER_BUTTON_POS_X );

const int   HEADER_TEXT_START_CHAR_Y    =   0;

const int   CHAPTER_TEXT_START_CHAR_Y   =   (detail::HEADER_DISP_MAX + 1) * ITEM_TEXT_1D_CHEIGHT;

const s16   PAGE_BUTTON_WIDTH           =  51;      // Page button width
const s16   PAGE_BUTTON_HEIGHT          =  20;      // Page button height

const s16   TOP_PANEL_POS_Y             = -87;      // Y-coordinate of the upper panel

const s16   BOTTOM_PANEL_POS_Y          =  86;      // Y-coordinate of the lower panel

const s16   TOP_PANEL_UI_POS_Y          = -86 - TOP_PANEL_POS_Y;    // Y-coordinate of UI elements on the upper panel

const s16   SCROLLBUTTON_POS_X          =  75;                      // X- and y-coordinates of the displayed scroll bar
const s16   SCROLLBUTTON_POS_Y          = -87 - TOP_PANEL_POS_Y;

const s16   TOP_PANEL_CLOSE_POS_Y       =  63;      // Y-coordinate when the upper panel is closed

// Distance to move panels up and down
const s16   PANEL_MOVE_MAX              = TOP_PANEL_CLOSE_POS_Y - TOP_PANEL_POS_Y;

const int   ITEM_PAGE_BUTTON_TEXT_HSPACE    = 0;    // Character spacing (in pixels) when rendering strings

const int   ITEMSPANEL_WIDTH            = GX_LCD_SIZE_X + ITEM_BUTTON_CENTER_DISTANCE;
const int   ITEMSPANEL_HEIGHT           = GX_LCD_SIZE_Y;

const int   UI_TEXT_HSPACE              =  0;    // Character spacing (in pixels) when rendering strings for UI elements

// Distance to move the scroll indicator horizontally on the Contents page
const s16   TOC_SCROLL_INDICATOR_MOVE_X     = 8 * 6;

// Starting horizontal position of the scroll indicator on the Contents page
const s16   TOC_SCROLL_INDICATOR_ORG_POS_X  = - TOC_SCROLL_INDICATOR_MOVE_X;


/*
    Structure that stores the results of processing Touch Panel actions on the control panel of the Contents page.
*/
struct ManualCtrlPanelResult
{
    u16                 eventType;
};

/*
    Display state
*/
enum
{
    HEADERITEMBACKSTATE_NORMAL,      // Normal
    HEADERITEMBACKSTATE_DOWNONLY,    // Lower side only

    HEADERITEMBACKSTATE_MAX
};

/*
    UI resource types
*/
enum
{
    UIRESKIND_JA,                   // Japanese resource
    UIRESKIND_EN,                   // North American / European resource

    UIRESKIND_MAX
};

// Button data used by the Control Panel for the Contents
struct ManualCtrlButtonData
{
    s16             buttonX;
    s16             buttonY;
    s16             buttonWidth;
    s16             buttonHeight;
    u16             buttonCell;
    u16             buttonAllowCell;
    
    u16             textIdx;
};

// Button text data used by the Control Panel for the Contents
struct ManualCtrlButtonTextData
{
    u16             cell;
    s16             canvasX;
    s16             canvasY;
    s16             canvasWidth;
    s16             canvasHeight;
};

// Array of button data
const ManualCtrlButtonData sButtonDataAry[UIRESKIND_MAX][detail::BUTTON_MAX] =
{
    {   // ja
        // UI elements to place in the upper panel
        { -118, TOP_PANEL_UI_POS_Y,  18, 18,                // Hint button
          NANR_nlv_skin_button_hint,       (u16)-1,
          (u16)-1, },
        {  -60, TOP_PANEL_UI_POS_Y,  93, 18,                // Close the manual
          NANR_nlv_skin_button_base_02,    (u16)-1,
          detail::CTRLPANELTEXT_CLOSE, },
        {    6, TOP_PANEL_UI_POS_Y,  22, 18,                // Button to open and close the Contents page
          NANR_nlv_skin_button_indexclose, NANR_nlv_skin_button_indexopen,
          (u16)-1, },

        // UI elements to place in the lower panel
        {  -56,  0,  64, 18,                                // Previous page
          NANR_nlv_skin_button_base_031,  (u16)-1, 
          detail::CTRLPANELTEXT_PREVPAGE, },
        {   56,  0,  64, 18,                                // Next page
          NANR_nlv_skin_button_base_041,  (u16)-1,
          detail::CTRLPANELTEXT_NEXTPAGE, },
    },
    {   // en
        // UI elements to place in the upper panel
        { -118, TOP_PANEL_UI_POS_Y,  18, 18,                // Hint button
          NANR_nlv_skin_button_hint,       (u16)-1,
          (u16)-1, },
        {  -60, TOP_PANEL_UI_POS_Y,  93, 18,                // Close the manual
          NANR_nlv_skin_button_base_02,    (u16)-1,
          detail::CTRLPANELTEXT_CLOSE, },
        {    6, TOP_PANEL_UI_POS_Y,  22, 18,                // Button to open and close the Contents page
          NANR_nlv_skin_button_indexclose, NANR_nlv_skin_button_indexopen,
          (u16)-1, },

        // UI elements to place in the lower panel
        {  -65,  0, 120, 18,                                // Previous page
          NANR_nlv_skin_button_base_03,  (u16)-1, 
          detail::CTRLPANELTEXT_PREVPAGE, },
        {   65,  0, 120, 18,                                // Next page
          NANR_nlv_skin_button_base_04,  (u16)-1,
          detail::CTRLPANELTEXT_NEXTPAGE, },
    },
};

// Array of button text data
const ManualCtrlButtonTextData sButtonTextDataAry[detail::CTRLPANELTEXT_MAX] =
{
    {
      NANR_nlv_skin_button_txt_10,      // Close the manual
         0, 39, 12,  2,
    },
    {
      NANR_nlv_skin_button_txt_11,      // Previous page
        24, 39, 16,  2,
    },
    {
      NANR_nlv_skin_button_txt_12,      // Next page
        24, 40, 16,  2,
    },
};

/*---------------------------------------------------------------------------*
  Name:         GetPointedButtonIdx

  Description:  Determines whether a UI element is pressed. If so, gets the index.
                

  Arguments:    uiElementPtrs:  Array of pointers to UI elements.
                elementNum:     Number of UI elements.
                point:          Position to check.

  Returns:      If a UI element has been pressed, returns its index.
                If one has not been pressed, returns -1.
 *---------------------------------------------------------------------------*/
s16
GetPointedButtonIdx(
    UIElement*  uiElementPtrs[],
    u16         elementNum,
    Point       point
)
{
    // Hit detection for the button
    for (s16 i = 0; i < elementNum; ++i)
    {
        if (uiElementPtrs[i]->Contains(point))
        {
            return i;
        }
    }

    return -1;  // Button has not been pressed.
}

/*---------------------------------------------------------------------------*
  Name:         GetStartChapterIndex

  Description:  Gets the section starting number.

  Arguments:    manHeader:  ManualHeader object

  Returns:      Returns the section starting number
 *---------------------------------------------------------------------------*/
int
GetStartChapterIndex(const ManualHeader& manHeader)
{
    int chapIdxMin = 0;
    if (0 != manHeader.GetHeaderFileName())
    {
        chapIdxMin = -1;
    }

    return chapIdxMin;
}

}   // Namespace

namespace detail
{

// Text offset for the header page button
struct HeaderPageButtonData
{
    u16             buttonAnimSeqNo;
    s16             posY;                   // Y-coordinate of the button
};

struct ItemsPanelData
{
    s16         basePositionX;
    s16         basePositionY;
    u16         pageItemCellAnimIdx;        // Animation sequence number for header and section buttons
    u16         arrowPageItemCellAnimIdx;   // Animation sequence number for header buttons with arrows
    u16         textCellAnimBaseIdx;        // Starting animation sequence number for header and section button text
    s16         itemButtonPosX;             // X-coordinate of header and section buttons
    s16         itemButtonWidth;            // Width of header and section buttons
    u16         itemDispMax;                // Maximum number of header and section buttons to display
    u16         uiAddNum;                   // Additional UI elements
};

namespace
{

const HeaderPageButtonData sHeaderPageButtonDataAry[TOC_ITEM_PAGE_BUTTON_NUM] =
{
    { NANR_nlv_skin_button_base_06, -59 - HEADER_BUTTON_BASE_POS_Y, },
    { NANR_nlv_skin_button_base_05,  79 - HEADER_BUTTON_BASE_POS_Y, },
};

const ItemsPanelData sTocItemViewData[detail::ITEMS_PANEL_MAX] =
{
    {                           0, HEADER_BUTTON_BASE_POS_Y,
      NANR_nlv_skin_button_base_01, NANR_nlv_skin_button_base_00,
      NANR_nlv_skin_button_txt_00,
      HEADER_BUTTON_POS_X,  186,
      HEADER_DISP_MAX,       1,
    },

    { ITEM_BUTTON_CENTER_DISTANCE, CHAPTER_BUTTON_BASE_POS_Y,
      NANR_nlv_skin_button_base_07, static_cast<u16>(-1),
      NANR_nlv_skin_button_txt_05,
      CHAPTER_BUTTON_POS_X, 186,
      CHAPTER_ITEM_DISP_MAX, 2,
    },
};

}   // Namespace

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::ItemsPanel

  Description:  Constructor.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
ItemsPanel::ItemsPanel()
:   mFonts(0),
    mItemNum(0),
    mItemStartIdx(0),
    mItemDispMax(0),
    mTocPageIdx(0),
    mTocPageMax(0),
    mSelectedItemIdx(-1),
    mPointedButtonIdx(-1),
    mItemBtns(0),
    mBtnScrlPos(0),
    mBtnScrlEndPos(0),
    mFocused(true)
{
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::Init

  Description:  Performs initialization.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::Init(
    const ObjectConstructor&    objectConstructor,
    ObjManager*                 pObjManager,
    const NNSG2dFont *const     fonts[],
    const ItemsPanelData&       data
)
{
    mPosition = Point(data.basePositionX, data.basePositionY);
    mSize     = Size(GX_LCD_SIZE_X, GX_LCD_SIZE_Y);

    mFonts = fonts;

    mItemDispMax = data.itemDispMax;
    mElementNum = static_cast<u16>(GetBaseElementNum() + data.uiAddNum);
    mElements = objectConstructor.NewArray<UIElement*>(mElementNum);
    NNS_NULL_ASSERT(mElements);

    mItemBtns = objectConstructor.NewArray<ManualButton*>(GetItemAllocNum());
    NNS_NULL_ASSERT(mItemBtns);

    mDrawTextIdx = objectConstructor.NewArray<s16>(GetItemAllocNum());
    NNS_NULL_ASSERT(mDrawTextIdx);

    for (int i = 0; i < mElementNum; ++i)
    {
        mElements[i] = 0;
    }

    // Initialize buttons for headers and sections
    for (u16 i = 0; i < GetItemAllocNum(); ++i)
    {
        const u16 textCellAnimIdx = static_cast<u16>(data.textCellAnimBaseIdx + i);

        mItemBtns[i] = objectConstructor.NewObj<ManualButton>();
        NNS_NULL_ASSERT(mItemBtns[i]);

        mItemBtns[i]->Init(
            Point(data.itemButtonPosX, static_cast<s16>(i * ITEM_BUTTON_LINEHEIGHT)),
            Size(data.itemButtonWidth, ITEM_BUTTON_HEIGHT),
            pObjManager->CreateCellAnim(data.pageItemCellAnimIdx),
            data.arrowPageItemCellAnimIdx != static_cast<u16>(-1) ?
                pObjManager->CreateCellAnim(data.arrowPageItemCellAnimIdx):
                ObjManager::InvalidCellHandle,
            pObjManager->CreateCellAnim(textCellAnimIdx)
        );

        mElements[detail::TOC_ELEMENT_BUTTON_ID + i] = mItemBtns[i];
        mDrawTextIdx[i] = -1;
    }

    // Initialize the Next Page button
    for (u16 i = 0; i < detail::TOC_ITEM_PAGE_BUTTON_NUM; ++i)
    {
        ObjButton *const pButton = objectConstructor.NewObj<ObjButton>();
        NNS_NULL_ASSERT(pButton);
        pButton->Init(
            Point(data.itemButtonPosX, sHeaderPageButtonDataAry[i].posY),
            Size(PAGE_BUTTON_WIDTH, PAGE_BUTTON_HEIGHT),
            pObjManager->CreateCellAnim(sHeaderPageButtonDataAry[i].buttonAnimSeqNo)
        );
        mElements[detail::TOC_ELEMENT_PAGE_BUTTON_ID + i] = pButton;
    }

    // Background for page header buttons
    static s16 sPageBackPosYs[] =
    {
        -65 - HEADER_BUTTON_BASE_POS_Y, 
         85 - HEADER_BUTTON_BASE_POS_Y
    };
    for (u16 i = 0; i < detail::TOC_ITEM_MASK_NUM; ++i)
    {
        UIStatic *const pPageBack = objectConstructor.NewObj<UIStatic>();
        NNS_NULL_ASSERT(pPageBack);
        pPageBack->Init(
            Point(-27, sPageBackPosYs[i]),
            Size(192, 32),
            pObjManager->CreateCellAnim(NANR_nlv_skin_button_mask));
        pPageBack->SetVisible(false);

        mElements[detail::TOC_ELEMENT_PAGE_BACK_ID + i] = pPageBack;
    }
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::Finalize

  Description:  Performs shutdown.

  Arguments:    objectConstructor:  A reference to an ObjectConstructor object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::Finalize(const ObjectConstructor& objectConstructor)
{
    // NTMV_LOG("ItemsPanel::Finalize()\n");

    objectConstructor.DeletePrimArray(mDrawTextIdx);
    objectConstructor.DeletePrimArray(mItemBtns);

    Base::Finalize(objectConstructor);
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::InitPage

  Description:  Initializes page information.

  Arguments:    itemNum:  Total number of headers and sections to display.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::InitPage(u16 itemNum)
{
    NNS_NULL_ASSERT(mFonts);

    mItemNum = itemNum;

    mTocPageMax = static_cast<u16>((mItemNum + mItemDispMax - 1) / mItemDispMax);    // maximum page count

    InitPageButton();

    SetTocPageIdx(0);           // set the initial page

    mSelectedItemIdx = -1;    // no selection
    mPointedButtonIdx = -1;     // nothing is being pointed at

    // clear the header and section button state
    for (int i = 0; i < GetItemAllocNum(); ++i)
    {
        mItemBtns[i]->SetChecked(false);
        mItemBtns[i]->SetVisible(false);
    }

}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::SetSelectedItemIdx

  Description:  Uses an index value to select a header or section button.

  Arguments:    newIdx:  Index of the header or section button to set.

  Returns:      Returns TRUE when the value was changed.
 *---------------------------------------------------------------------------*/
bool
ItemsPanel::SetSelectedItemIdx(s16 newIdx)
{
    if (mSelectedItemIdx == newIdx && IsFocused())
    {
        return false;
    }

    SetFocused(true);

    UpdateItemButtonState(false);

    mSelectedItemIdx = newIdx;

    UpdateItemButtonState(true);

    return true;
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::EnsureSelectedItemVisible

  Description:  Switches page to display to make selected header/section items visible.
                

  Arguments:    None.

  Returns:      Returns TRUE when the page index was changed.
 *---------------------------------------------------------------------------*/
bool
ItemsPanel::EnsureSelectedItemVisible()
{
    if (mSelectedItemIdx != -1)
    {
        const s16 newTocPageIdx = static_cast<s16>(mSelectedItemIdx / mItemDispMax);
        return ChangeTocPageIdx(newTocPageIdx);
    }

    return false;
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::TestTouchPanel

  Description:  Checks Touch Panel content and updates its state.

  Arguments:    pUpdateResult:  Pointer to a structure to store the update results.
                tpData:         Touch Panel data.

  Returns:      Returns TRUE when the Touch Panel was processed.
 *---------------------------------------------------------------------------*/
bool
ItemsPanel::TestTouchPanel(
    UpdateResult*           pUpdateResult,
    const TouchPanelData&   tpData
)
{
    bool bHandled = false;  // TRUE when the Touch Panel is processed

    pUpdateResult->bPageChanged  = false;
    pUpdateResult->bSelectChanged = false;
    pUpdateResult->bClicked = false;
    pUpdateResult->bTouchOn = false;
    pUpdateResult->bEnabled = false;

    if (! IsVisible() || IsBusy())
    {
        return bHandled;
    }

    if (tpData.bTouchOn)
    {
        if (tpData.bTouchOnTrigger)
        {
            const s16 hitBtn = GetPointedButtonIdx(tpData.point);
            if (hitBtn != -1)
            {
                bHandled = true;

                if (mElements[hitBtn]->IsEnabled())
                {
                    mPointedButtonIdx = hitBtn; // Record the button that was pointed at
                    pUpdateResult->bEnabled = true;
                    pUpdateResult->bTouchOn = true;
                }
                else
                {
                    const bool bPageBack = TOC_ELEMENT_PAGE_BACK_ID <= hitBtn && hitBtn < TOC_ELEMENT_BUTTON_ID;
                    if (! bPageBack)
                    {
                        pUpdateResult->bTouchOn = true;
                    }
                }
            }
        }

        if (mPointedButtonIdx != -1)
        {
            bHandled = true;

            // Is the button that was originally pressed currently being pointed at?
            mElements[mPointedButtonIdx]->CheckHit(tpData.point);
        }
    }
    else if (tpData.bTouchOffTrigger)
    {
        if (mPointedButtonIdx != -1)
        {
            const s16 btnIdx = mPointedButtonIdx;
            mPointedButtonIdx = -1; // clear the selection state

            bHandled = true;

            if (mElements[btnIdx]->IsPointed())
            {
                mElements[btnIdx]->SetPointed(false);  // clear the selection state

                if (btnIdx < TOC_ELEMENT_PAGE_BACK_ID)  // Next Page button
                {
                    s16 nextPageIdx;
                    if (btnIdx == TOC_ELEMENT_PAGE_BUTTON_ID)   // "Previous" button
                    {
                        nextPageIdx = static_cast<s16>(mTocPageIdx - 1);
                    }
                    else    // "Next" button
                    {
                        nextPageIdx = static_cast<s16>(mTocPageIdx + 1);
                    }
                    pUpdateResult->bPageChanged  = ChangeTocPageIdx(nextPageIdx);
                }
                else if (btnIdx >= TOC_ELEMENT_BUTTON_ID)   // Header button
                {
                    const s16 itemIdx = static_cast<s16>(
                          mItemStartIdx
                        + (btnIdx - TOC_ELEMENT_BUTTON_ID + GetItemAllocNum() - (mItemStartIdx % GetItemAllocNum())) % GetItemAllocNum());
                    pUpdateResult->bClicked = true;
                    pUpdateResult->bSelectChanged = SetSelectedItemIdx(itemIdx);
                }
            }
        }
    }

    return bHandled;
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::InitPageButton

  Description:  Initializes page buttons.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::InitPageButton()
{
    const bool bPageBtnVisible = mTocPageMax >= 2;

    // Initialize Next Page button
    for (u16 i = 0; i < TOC_ITEM_PAGE_BUTTON_NUM; ++i)
    {
        mElements[TOC_ELEMENT_PAGE_BUTTON_ID + i]->SetVisible(bPageBtnVisible); // display/hide buttons
    }

    for (u16 i = 0; i < TOC_ITEM_MASK_NUM; ++i)
    {
        mElements[TOC_ELEMENT_PAGE_BACK_ID + i]->SetVisible(bPageBtnVisible);   // display/hide the background

        // disable this so that it is not pressed
        mElements[TOC_ELEMENT_PAGE_BACK_ID + i]->SetEnabled(false);
    }
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::SetTocPageIdx

  Description:  Sets the index for the Contents page.

  Arguments:    tocPageIdx:  Index for the Contents page.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::SetTocPageIdx(s16 tocPageIdx)
{
    mTocPageIdx = tocPageIdx;
    mItemStartIdx = static_cast<s16>(mTocPageIdx * mItemDispMax);

    UpdatePageButton();
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::ChangeTocPageIdx

  Description:  Changes the index for the Contents page.

  Arguments:    tocPageIdx:  Index for the Contents page.

  Returns:      Returns TRUE when the page index was changed.
 *---------------------------------------------------------------------------*/
bool
ItemsPanel::ChangeTocPageIdx(s16 tocPageIdx)
{
    NNS_ASSERT(tocPageIdx < mTocPageMax);

    if (mTocPageIdx == tocPageIdx)
    {
        return false;
    }

    SetTocPageIdx(tocPageIdx);

    mBtnScrlEndPos = static_cast<s16>(ITEM_BUTTON_LINEHEIGHT * mItemStartIdx);

    return true;
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::UpdateItemButtonState

  Description:  Updates the selection state of a header or section button.

  Arguments:    bChecked:  TRUE when a selected header or section button will be set to the selected state.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::UpdateItemButtonState(bool bChecked)
{
    if (mSelectedItemIdx != -1 && IsFocused())
    {
        // When the selected item is visible
        if ( mSelectedItemIdx >= mItemStartIdx
          && mSelectedItemIdx <  mItemStartIdx + mItemDispMax
        )
        {
            mItemBtns[mSelectedItemIdx % GetItemAllocNum()]->SetChecked(bChecked);
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::UpdatePageButton

  Description:  Initializes page buttons.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::UpdatePageButton()
{
    NNS_NULL_ASSERT(mFonts);

    if (mTocPageMax <= 1)   // do nothing when there is only one page
    {
        return;
    }

    // Enable/disable header page buttons
    mElements[detail::TOC_ELEMENT_PAGE_BUTTON_ID + 0]->SetEnabled(mTocPageIdx > 0);
    mElements[detail::TOC_ELEMENT_PAGE_BUTTON_ID + 1]->SetEnabled(mTocPageIdx + 1 < mTocPageMax);
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::DrawHeaderButtonText

  Description:  Draws button text.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::DrawHeaderButtonText(
    const DrawTextInfo  drawTextInfoAry[],
    u16                 textNum,
    s16                 startHeaderIdx
)
{
    NNS_NULL_ASSERT(mFonts);

    const int enableNum = Min(mItemDispMax, textNum);

    // Set buttons for the header
    for (int i = 0; i < GetItemAllocNum(); ++i)
    {
        const s16 headerIdx = static_cast<s16>(startHeaderIdx + i);
        const int slot = headerIdx % GetItemAllocNum();
        if (i < enableNum)
        {
            DrawHeaderButtonText(drawTextInfoAry[i], headerIdx);
        }
        else
        {
            mItemBtns[slot]->SetVisible(false);
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::DrawHeaderButtonText

  Description:  Draws button text.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::DrawHeaderButtonText(
    const DrawTextInfo& drawTextInfo,
    s16                 headerIdx
)
{
    NNS_NULL_ASSERT(mFonts);

    const s32 slot = headerIdx % GetItemAllocNum();
    const s16 textCharY = static_cast<s16>(HEADER_TEXT_START_CHAR_Y + ITEM_TEXT_1D_CHEIGHT * slot);

    TextCanvasContext numCanvasContext(
        false,
        Point(0, textCharY),
        Size(HEADER_NUM_CWIDTH, ITEM_TEXT_CHEIGHT),
        mFonts[FONTSIZE_12],
        detail::PAGEITEM_TEXT_HSPACE);

    // String for heading numbers
    //   Create a string
    const int numTextBufSize = 9 + 1;   // 9 digits \0: This will not overflow because it allocates enough memory for the maximum number of digits in a u32 value.
                                        //           
    wchar_t numTextBuf[numTextBufSize];
    std::swprintf(numTextBuf, numTextBufSize, L"%u", headerIdx + 1);
    NNS_G2dTextCanvasDrawTextRect(
        numCanvasContext.GetTextCanvas(),
        0,
        0,
        HEADER_NUM_CWIDTH * 8,
        ITEM_TEXT_CHEIGHT * 8,
        TEXT_COLOR_FG,
        NNS_G2D_HORIZONTALALIGN_CENTER | NNS_G2D_VERTICALALIGN_MIDDLE,
        numTextBuf
    );

    // Number of characters in the numeric part
    const int numCharaNum = HEADER_NUM_CWIDTH * ITEM_TEXT_CHEIGHT;

    // Position (in characters) to display the textual part
    Point textPos(
        static_cast<s16>(            numCharaNum % ITEM_TEXT_1D_CWIDTH),
        static_cast<s16>(textCharY + numCharaNum / ITEM_TEXT_1D_CWIDTH)
    );

    TextCanvasContext textCanvasContext(
        false,
        textPos,
        Size(HEADER_TEXT_CWIDTH, ITEM_TEXT_CHEIGHT),
        mFonts[FONTSIZE_12],
        detail::PAGEITEM_TEXT_HSPACE);

    // Heading string
    NNS_G2dTextCanvasDrawText(
        textCanvasContext.GetTextCanvas(),
        0,
        ITEM_TEXT_CHEIGHT * 8 / 2,
        TEXT_COLOR_FG,
        NNS_G2D_HORIZONTALORIGIN_LEFT | NNS_G2D_VERTICALORIGIN_MIDDLE | NNS_G2D_HORIZONTALALIGN_LEFT,
        drawTextInfo.text
    );

    mItemBtns[slot]->SetAllow(drawTextInfo.bArrow); // Headers with sections are of the Allow type
    mItemBtns[slot]->SetVisible(true);
    mDrawTextIdx[slot] = headerIdx;
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::DrawChapterButtonText

  Description:  Draws button text.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::DrawChapterButtonText(
    const DrawTextInfo  drawTextInfoAry[],
    u16                 textNum,
    s16                 startChapterIdx
)
{
    NNS_NULL_ASSERT(mFonts);

    const int enableNum = Min(mItemDispMax, textNum);

    // Set buttons for sections
    for (int i = 0; i < GetItemAllocNum(); ++i)
    {
        const s16 chapterIdx = static_cast<s16>(startChapterIdx + i);
        const int slot = chapterIdx % GetItemAllocNum();
        if (i < enableNum)
        {
            const s16 textCharY = static_cast<s16>(CHAPTER_TEXT_START_CHAR_Y + ITEM_TEXT_1D_CHEIGHT * slot);

            TextCanvasContext textCanvasContext(
                false,
                Point(0, textCharY),
                Size(CHAPTER_TEXT_CWIDTH, ITEM_TEXT_CHEIGHT),
                mFonts[FONTSIZE_12],
                detail::PAGEITEM_TEXT_HSPACE);

            // Heading string
            NNS_G2dTextCanvasDrawText(
                textCanvasContext.GetTextCanvas(),
                0,
                ITEM_TEXT_CHEIGHT * 8 / 2,
                TEXT_COLOR_FG,
                NNS_G2D_HORIZONTALORIGIN_LEFT | NNS_G2D_VERTICALORIGIN_MIDDLE | NNS_G2D_HORIZONTALALIGN_LEFT,
                drawTextInfoAry[i].text
            );

            mItemBtns[slot]->SetAllow(drawTextInfoAry[i].bArrow); // Headers with sections are of the Allow type
            mItemBtns[slot]->SetVisible(true);
            mDrawTextIdx[slot] = chapterIdx;
        }
        else
        {
            mItemBtns[slot]->SetVisible(false);
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::SetUI

  Description:  Sets UI elements.

  Arguments:    index:      Index for the UI element to set.
                uiElement:  Pointer to the UI element.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ItemsPanel::SetUI(
    u16         index,
    UIElement*  uiElement
)
{
    NNS_ASSERT(GetBaseElementNum() + index < mElementNum);
    NNS_NULL_ASSERT(uiElement);

    mElements[GetBaseElementNum() + index] = uiElement;
}

/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::GetPointedButtonIdx

  Description:  Determines whether the Contents button is pressed. If so, gets the index of the button.
                

  Arguments:    point:  Position to check.

  Returns:      If a button in the Contents page has been pressed, returns its index.
                If one has not been pressed, returns -1.
 *---------------------------------------------------------------------------*/
s16
ItemsPanel::GetPointedButtonIdx(Point point) const
{
    // Hit detection for the button
    for (s16 i = 0; i < GetBaseElementNum(); ++i)
    {
        if (mElements[i]->Contains(point))
        {
            return i;
        }
    }

    return -1;  // Button has not been pressed.
}


/*---------------------------------------------------------------------------*
  Name:         ItemsPanel::Animate

  Description:  Animates a header.

  Arguments:    headerNum: The number of headers.

  Returns:      TRUE when the state changed.
 *---------------------------------------------------------------------------*/
bool
ItemsPanel::AnimateHeader(
    DrawHeaderTextCallback  cb,
    void*                   userData
    )
{
    if (mBtnScrlPos == mBtnScrlEndPos)
    {
        return false;
    }

    const s16 btnMoveValMax = 12;
    if (mBtnScrlPos < mBtnScrlEndPos)
    {
        mBtnScrlPos = Min(static_cast<s16>(mBtnScrlPos + btnMoveValMax), mBtnScrlEndPos);
    }
    else
    {
        mBtnScrlPos = Max(static_cast<s16>(mBtnScrlPos - btnMoveValMax), mBtnScrlEndPos);
    }

    const int headIdxStart = mBtnScrlPos / ITEM_BUTTON_LINEHEIGHT;

    for (int i = 0; i < GetItemAllocNum(); ++i)
    {
        const s16 headIdx = static_cast<s16>(headIdxStart + i);
        const bool bVisible = headIdx < mItemNum;

        const int slot = headIdx % GetItemAllocNum();
        mItemBtns[slot]->SetVisible(bVisible);
        if (bVisible)
        {
            Point pos = mItemBtns[slot]->GetPosition();
            pos.y = static_cast<s16>(headIdx * ITEM_BUTTON_LINEHEIGHT - mBtnScrlPos);
            mItemBtns[slot]->SetPosition(pos);
            mItemBtns[slot]->SetChecked(-1 != GetSelectedItemIdx() && IsFocused() && headIdx == GetSelectedItemIdx());

            if (mDrawTextIdx[slot] != headIdx)
            {
                DrawHeaderButtonText(cb(userData, headIdx), headIdx);
            }
        }
    }

    return true;
}

}   // namespace detail

namespace
{

/*---------------------------------------------------------------------------*
  Name:         DrawHeaderTextCallback

  Description:  Callback function that generates information for the header to draw.

  Arguments:    userData:  A pointer to a ManualTableOfContents object.
                headerIdx: The header index.

  Returns:      Returns a DrawTextInfo object.
 *---------------------------------------------------------------------------*/
const detail::ItemsPanel::DrawTextInfo
DrawHeaderTextCallback(
    void*   userData,
    s16     headerIdx
)
{
    NNS_NULL_ASSERT(userData);

    ManualTableOfContents *const pManualToc = static_cast<ManualTableOfContents*>(userData);

    detail::ItemsPanel::DrawTextInfo drawTextInfo;
    const ManualHeader& manHeader = pManualToc->GetHeaders()[headerIdx];
    drawTextInfo.text = manHeader.GetHeaderText();
    if (drawTextInfo.text[0] == L'\0')
    {
        drawTextInfo.text = L"(---)";
    }
    drawTextInfo.bArrow = manHeader.GetChapterNum() > 0;
    return drawTextInfo;
}

}   // Namespace

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::ManualTocPanel

  Description:  Constructor.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
ManualTocPanel::ManualTocPanel()
:   mpManualToc(0),
    mpObjManager(0),
    mpSndMgr(0),
    mItemsPanel(0),
    mManualItemBack(0),
    mHeadRetCover(0),
    mChapRetCover(0),
    mScrollButton(0),
    mPointedUIIdx(-1),
    mHScrollTargetPos(ITEM_BUTTON_CENTER_DISTANCE),
    mItemViewMode(detail::ITEMVIEWMODE_HEADERONLY),
    mTocIndScrollTargetPos(TOC_SCROLL_INDICATOR_ORG_POS_X),
    mbHelpSelected(false),
    mbOpening(false),
    mbDispItemBack(false)
{
    for (int i = 0; i < detail::ITEMS_PANEL_MAX; ++i)
    {
        mItemsPanels[i] = 0;
    }

    for (int i = 0; i < detail::UI_MAX; ++i)
    {
        mHitItems[i] = 0;
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::Init

  Description:  Performs initialization. Sets the next content.
                    - Sets the font array
                    - Initializes buttons

  Arguments:    objectConstructor: A reference to an ObjectConstructor object.
                pObjManager:       A pointer to an ObjManager object.
                fonts:             Font array.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::Init(
    const ObjectConstructor&    objectConstructor,
    ObjManager*                 pObjManager,
    const NNSG2dFont *const     fonts[],
    s16                         pagePrevButtonW,
    const wchar_t*              ctrlPanelTexts[],
    SoundManager*               pSndMgr
)
{
    mpObjManager = pObjManager;
    mpSndMgr = pSndMgr;

    mPosition.Set(128, 96);
    mSize.Set(GX_LCD_SIZE_X, GX_LCD_SIZE_Y);

    const int uiResIdx = pagePrevButtonW <= sButtonDataAry[UIRESKIND_JA][detail::BUTTON_PREV].buttonWidth ?
        UIRESKIND_JA: UIRESKIND_EN;
    
    // Initialize buttons for the upper and lower control panels
    for (int i = 0; i < detail::BUTTON_MAX; ++i)
    {
        ManualButton* pButton = objectConstructor.NewObj<ManualButton>();
        NNS_NULL_ASSERT(pButton);

        const ManualCtrlButtonData *const pBtnData = &sButtonDataAry[uiResIdx][i];

        const ManualCtrlButtonTextData *const pTextData =
            pBtnData->textIdx == (u16)-1 ?
                0:
                &sButtonTextDataAry[pBtnData->textIdx];

        pButton->Init(
            Point(pBtnData->buttonX, pBtnData->buttonY),
            Size(pBtnData->buttonWidth, pBtnData->buttonHeight),
            pObjManager->CreateCellAnim(pBtnData->buttonCell),
            pBtnData->buttonAllowCell == (u16)-1 ?
                ObjManager::InvalidCellHandle:
                pObjManager->CreateCellAnim(pBtnData->buttonAllowCell),
            pTextData ?
                pObjManager->CreateCellAnim(pTextData->cell):
                ObjManager::InvalidCellHandle
        );

        mHitItems[i] = pButton;

        // Render character string
        if (pTextData)
        {
            TextCanvasContext textCanvasContext(
                false,
                Point(pTextData->canvasX, pTextData->canvasY),
                Size(pTextData->canvasWidth, pTextData->canvasHeight),
                fonts[FONTSIZE_12],
                UI_TEXT_HSPACE);
            NNS_G2dTextCanvasDrawTextRect(
                textCanvasContext.GetTextCanvas(),
                0, 0,
                pTextData->canvasWidth * 8, pTextData->canvasHeight * 8,
                TEXT_COLOR_FG,
                NNS_G2D_HORIZONTALALIGN_CENTER | NNS_G2D_VERTICALALIGN_MIDDLE,
                ctrlPanelTexts[pBtnData->textIdx]
            );
        }
    }

    {
        {
            for (int i = 0; i < detail::ITEMS_PANEL_MAX; ++i)
            {
                mItemsPanels[i] = objectConstructor.NewObj<detail::ItemsPanel>();
                NNS_NULL_ASSERT(mItemsPanels[i]);

                mItemsPanels[i]->Init(objectConstructor, pObjManager, fonts, detail::sTocItemViewData[i]);
            }

            // Background for page header buttons
            mManualItemBack = objectConstructor.NewObj<UIStatic>();
            NNS_NULL_ASSERT(mManualItemBack);
            mManualItemBack->Init(
                Point(HEADER_BUTTON_POS_X - ITEM_BUTTON_CENTER_DISTANCE, 0 * ITEM_BUTTON_LINEHEIGHT),
                Size(12 * 2 * 8, 26),
                pObjManager->CreateCellAnim(NANR_nlv_skin_button_joint_00));
            mManualItemBack->SetAnimFrame(pObjManager, HEADERITEMBACKSTATE_DOWNONLY);
            mManualItemBack->SetVisible(false);

            mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->SetUI(0, mManualItemBack);

            // Move the header view
            mHeadRetCover = objectConstructor.NewObj<UIElement>(
                Point(HEADER_BUTTON_POS_X, -HEADER_BUTTON_BASE_POS_Y),
                Size(detail::sTocItemViewData[detail::ITEMS_PANEL_HEADER].itemButtonWidth, GX_LCD_SIZE_Y));
            NNS_NULL_ASSERT(mHeadRetCover);
            mItemsPanels[detail::ITEMS_PANEL_HEADER]->SetUI(0, mHeadRetCover);
            mHitItems[detail::HEADRETCOVER] = mHeadRetCover;

            // Move the section view
            mChapRetCover = objectConstructor.NewObj<UIElement>(
                Point(CHAPTER_BUTTON_POS_X, -CHAPTER_BUTTON_BASE_POS_Y),
                Size(detail::sTocItemViewData[detail::ITEMS_PANEL_CHAPTER].itemButtonWidth, GX_LCD_SIZE_Y));
            NNS_NULL_ASSERT(mChapRetCover);
            mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->SetUI(1, mChapRetCover);
            mHitItems[detail::CHAPRETCOVER] = mChapRetCover;

            UIElement* elements[] = { mItemsPanels[0], mItemsPanels[1] };
            mItemsPanel = objectConstructor.NewObj<UIPanel>();
            NNS_NULL_ASSERT(mItemsPanel);
            mItemsPanel->Init(
                objectConstructor,
                elements,
                sizeof(elements) / sizeof(elements[0]),
                Point(0, 0),
                Size(ITEMSPANEL_WIDTH, ITEMSPANEL_HEIGHT));
        }

        UIPanel *const topPanel = objectConstructor.NewObj<UIPanel>();
        NNS_NULL_ASSERT(topPanel);

        {
            // Scroll buttons
            mScrollButton = objectConstructor.NewObj<ScrollButton>();
            NNS_NULL_ASSERT(mScrollButton);
            mScrollButton->Init(
                objectConstructor,
                pObjManager,
                Point(SCROLLBUTTON_POS_X, SCROLLBUTTON_POS_Y),
                detail::UPPAGEVIEW_HEIGHT);

            UIStatic *const topPanelBack = objectConstructor.NewObj<UIStatic>();
            NNS_NULL_ASSERT(topPanelBack);
            topPanelBack->Init(
                Point(0, 0),
                Size(GX_LCD_SIZE_X, detail::TOCVIEWTOPCTRL_HEIGHT),
                pObjManager->CreateCellAnim(NANR_nlv_skin_button_back));
            topPanelBack->SetEnabled(false);
            mHitItems[detail::TOP_PANEL_BACK] = topPanelBack;

            UIElement* elements[] = {
                mHitItems[detail::BUTTON_HELP],
                mHitItems[detail::BUTTON_EXIT],
                mHitItems[detail::BUTTON_DISP],
                mScrollButton,
                mHitItems[detail::TOP_PANEL_BACK],
            };

            topPanel->Init(
                objectConstructor,
                elements,
                sizeof(elements) / sizeof(elements[0]),
                Point(0, TOP_PANEL_POS_Y),
                Size(detail::TOCVIEW_WIDTH, detail::TOCVIEWTOPCTRL_HEIGHT));
        }

        {
            UIElement* elements[] = {
                topPanel,
                mItemsPanel
            };

            mWholePanel.Init(
                objectConstructor, 
                elements,
                sizeof(elements) / sizeof(elements[0]),
                Point(0, PANEL_MOVE_MAX),   // state with the initial position closed
                Size(detail::TOCVIEW_WIDTH, detail::TOCVIEW_HEIGHT_MAX - detail::TOCVIEWBTMCTRL_HEIGHT));
        }
    }

    {
        UIStatic *const btmPanelBack = objectConstructor.NewObj<UIStatic>();
        NNS_NULL_ASSERT(btmPanelBack);
        btmPanelBack->Init(
            Point(0, 0),
            Size(GX_LCD_SIZE_X, detail::TOCVIEWBTMCTRL_HEIGHT),
            pObjManager->CreateCellAnim(NANR_nlv_skin_button_back_under));
        btmPanelBack->SetEnabled(false);
        mHitItems[detail::BTM_PANEL_BACK] = btmPanelBack;

        UIElement* elements[] =
        {
            mHitItems[detail::BUTTON_PREV],
            mHitItems[detail::BUTTON_NEXT],
            mHitItems[detail::BTM_PANEL_BACK],
        };

        mBtmPanel.Init(
            objectConstructor,
            elements,
            sizeof(elements) / sizeof(elements[0]),
            Point(0, BOTTOM_PANEL_POS_Y),
            Size(detail::TOCVIEW_WIDTH, detail::TOCVIEWBTMCTRL_HEIGHT));
    }

    // Initial settings
    SetNextPrevItemButtonState(false, false);
    SetItemViewMode(detail::ITEMVIEWMODE_HEADERONLY);

    mBgPoss[0].Set(TOC_SCROLL_INDICATOR_ORG_POS_X, 0);
    mBgPoss[1].Set(ITEM_BUTTON_CENTER_DISTANCE, 0 - CHAPTER_BUTTON_BASE_POS_Y);

    SetOpened(true);    // start in the open state
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::Finalize

  Description:  Performs shutdown.

  Arguments:    objectConstructor:  A reference to an ObjectConstructor object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::Finalize(const ObjectConstructor& objectConstructor)
{
    // NTMV_LOG("ManualTocPanel::Finalize()\n");

    mBtmPanel.Finalize(objectConstructor);
    mWholePanel.Finalize(objectConstructor);

    Base::Finalize(objectConstructor);
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::Draw

  Description:  Performs rendering.

  Arguments:    info:      A reference to an ObjG2dInfo object.
                parentPos: Parent position (global coordinates).

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::Draw(
    const ObjG2dInfo&   info,
    Point               parentPos
)
{
    if (! IsVisible())
    {
        return;
    }

    CalcGlbPosition(parentPos);

    NNS_NULL_ASSERT(mpManualToc);

    mWholePanel.Draw(info, mGlbPosition);
    mBtmPanel.Draw(info, mGlbPosition);

    // Sets the display position for the background screen of the Contents page
    {
        Point pos = mWholePanel.GetGlbPosition() - mGlbPosition;
        mBgPoss[0].y = pos.y;
        mBgPoss[2].y = pos.y;
    }
    {
        Point pos = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetGlbPosition() - mGlbPosition;
        // When only headers are displayed, move the section background to a location where it cannot be seen.
        if (mItemViewMode == detail::ITEMVIEWMODE_HEADERONLY)
        {
            mBgPoss[1].Set(64,    static_cast<s16>(pos.y - HEADER_BUTTON_BASE_POS_Y));
        }
        else
        {
            mBgPoss[1].Set(pos.x, static_cast<s16>(pos.y - HEADER_BUTTON_BASE_POS_Y));
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::SetupManualTableOfContents

  Description:  Sets data for the manual's Contents page and builds the page's content so that the contents page data can be displayed.
                

  Arguments:    pManToc:  Contents page data.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::SetupManualTableOfContents(ManualTableOfContents* pManToc)
{
    mpManualToc = pManToc;

    // Header page settings
    mItemsPanels[detail::ITEMS_PANEL_HEADER]->InitPage(mpManualToc->GetHeaderNum());
    DrawHeaderButtonText();

    //   Set the buttons that are initially selected
    if (mpManualToc->GetHeaderNum() > 0)
    {
        const int headerIdx = 0;
        SetSelectedHeaderIdx(headerIdx);
        UpdateItemViewMode(true);
    }
    else
    {
        InitChapterPage();
    }

    UpdateNextPrevItemButtonState();
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::TestTouchPanel

  Description:  Checks Touch Panel content and updates its state.

  Arguments:    pObjManager: A pointer to an ObjManager object.
                tpData:      Touch Panel data.

  Returns:      Returns TRUE when the Touch Panel was processed.
 *---------------------------------------------------------------------------*/
bool
ManualTocPanel::TestTouchPanel(
    ManualTocTouchResult*   pResult,
    const TouchPanelData&   tpData
)
{
    bool bHandled = false;

    pResult->eventType = MANUALTOCEVENT_NONE;
    pResult->state     = 0;

    if (! IsVisible() || IsBusy())
    {
        return bHandled;
    }

    detail::ItemsPanel::UpdateResult result[detail::ITEMS_PANEL_MAX];

    ManualTocTouchResult ctrlPanelResult;
    bHandled = HitTestCtrlPanel(&ctrlPanelResult, tpData);
    if (bHandled)
    {
        pResult->eventType = ctrlPanelResult.eventType;
        pResult->state     = ctrlPanelResult.state;
    }
    else
    {
        for (int i = 0; i < detail::ITEMS_PANEL_MAX; ++i)
        {
            if (mItemsPanels[i]->TestTouchPanel(&result[i], tpData))
            {
                bHandled = true;

                if (result[i].bTouchOn)
                {
                    mpSndMgr->startSe(
                        result[i].bEnabled ?
                            TWL_MAN_SE_TOUCH_ON:
                            TWL_MAN_SE_INVALID
                    );
                }
            }
        }

        if (bHandled)
        {
            if (result[detail::ITEMS_PANEL_HEADER].bPageChanged)
            {
                UpdateItemViewMode(true);
                mpSndMgr->startSe(TWL_MAN_SE_SUBMENU_OPEN);
            }
            else if (result[detail::ITEMS_PANEL_CHAPTER].bPageChanged)
            {
                DrawChapterButtonText();
                mpSndMgr->startSe(TWL_MAN_SE_SUBMENU_OPEN);
            }
            else if (result[detail::ITEMS_PANEL_HEADER].bSelectChanged)
            {
                SetHelpButtonSelected(false);
                UpdateItemViewMode();

                InitChapterPage();

                UpdateNextPrevItemButtonState();
                pResult->eventType = MANUALTOCEVENT_HEADERSELECTCHANGED;
                mpSndMgr->startSe(TWL_MAN_SE_SELECT);
            }
            else if (result[detail::ITEMS_PANEL_CHAPTER].bSelectChanged)
            {
                SetHelpButtonSelected(false);
                UpdateNextPrevItemButtonState();
                pResult->eventType = MANUALTOCEVENT_CHAPTERSELECTCHANGED;
                mpSndMgr->startSe(TWL_MAN_SE_SELECT);
            }
            else if (result[detail::ITEMS_PANEL_HEADER].bClicked)
            {
                SetHelpButtonSelected(false);
                // When a header has sections, display the sections.
                const int headerIdx = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetSelectedItemIdx();
                const ManualHeader& manHeader = mpManualToc->GetHeaders()[headerIdx];
                if (0 < manHeader.GetChapterNum())
                {
                    UpdateItemViewMode();

                    const int firstChapIdx = GetStartChapterIndex(manHeader);
                    if (firstChapIdx != mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetSelectedItemIdx())
                    {
                        InitChapterPage();

                        UpdateNextPrevItemButtonState();
                        pResult->eventType = MANUALTOCEVENT_HEADERSELECTCHANGED;
                        mpSndMgr->startSe(TWL_MAN_SE_SELECT);
                    }
                }
                else
                {
                    // When there is no reaction
                    mpSndMgr->startSe(TWL_MAN_SE_TOUCH_OFF);
                }
            }
            else if (result[detail::ITEMS_PANEL_CHAPTER].bClicked)
            {
                // When there is no reaction
                mpSndMgr->startSe(TWL_MAN_SE_TOUCH_OFF);
            }
        }
        else
        {
            if (tpData.bTouchOnTrigger || tpData.bTouchOffTrigger)
            {
                if (mWholePanel.Contains(tpData.point)) // an empty area was tapped
                {
                    bHandled = true;
                }
            }
        }
    }

    return bHandled;
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::GetSelectedHeader

  Description:  Gets the selected header.

  Arguments:    None.

  Returns:      Returns the selected header.
                Returns NULL if no header is selected.
 *---------------------------------------------------------------------------*/
const ManualHeader*
ManualTocPanel::GetSelectedHeader() const
{
    const s16 selectedHeaderIdx = GetSelectedHeaderIdx();
    if (selectedHeaderIdx == -1)
    {
        return 0;
    }

    return &mpManualToc->GetHeaders()[selectedHeaderIdx];
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::GetSelectedHeaderIdx

  Description:  Get the index of the selected header.

  Arguments:    None.

  Returns:      Returns the index of the selected header.
                Returns -1 if no header is selected.
 *---------------------------------------------------------------------------*/
s16
ManualTocPanel::GetSelectedHeaderIdx() const
{
    return mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetSelectedItemIdx();
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::SetSelectedHeaderIdx

  Description:  Uses an index to set the selected header.

  Arguments:    value:  The header index.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::SetSelectedHeaderIdx(s16 value)
{
    if (mItemsPanels[detail::ITEMS_PANEL_HEADER]->SetSelectedItemIdx(value))
    {
        mItemsPanels[detail::ITEMS_PANEL_HEADER]->EnsureSelectedItemVisible();

        InitChapterPage();
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::GetSelectedChapterIdx

  Description:  Get the index of the selected section.

  Arguments:    None.

  Returns:      Returns the index of the selected section.
                Returns -1 if no section is selected.
 *---------------------------------------------------------------------------*/
s16
ManualTocPanel::GetSelectedChapterIdx() const
{
    return mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetSelectedItemIdx();
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::SetSelectedChapterIdx

  Description:  Uses an index to set the selected header.

  Arguments:    value:  The header index.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::SetSelectedChapterIdx(s16 value)
{
    if (mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->SetSelectedItemIdx(value))
    {
        if (mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->EnsureSelectedItemVisible())
        {
            DrawChapterButtonText();
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::SetOpened

  Description:  Sets the state of the Contents panel: open or closed.

  Arguments:    value:  When TRUE, opens the Contents panel.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::SetOpened(bool value)
{
    mbOpening = value;
    // Change the style of the button that opens and closes the Contents panel (the opened and closed states are opposite)
    static_cast<ManualButton*>(mHitItems[detail::BUTTON_DISP])->SetAllow(! mbOpening);
}


/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::Animate

  Description:  Updates the animation state.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::Animate()
{
    const s16 moveValMax = 32;

    Point pos = mWholePanel.GetPosition();
    if (mbOpening)
    {
        if (pos.y > 0)
        {
            pos.y = static_cast<s16>(Max(pos.y - moveValMax, 0));
            mWholePanel.SetPosition(pos);
        }
    }
    else
    {
        if (pos.y < PANEL_MOVE_MAX)
        {
            pos.y = Min(static_cast<s16>(pos.y + moveValMax), PANEL_MOVE_MAX);
            mWholePanel.SetPosition(pos);
        }
    }
    {
        // Adjust the position of the lower panel to match the position of mWholePanel
        Point btmPos = mBtmPanel.GetPosition();
        btmPos.y = static_cast<s16>(BOTTOM_PANEL_POS_Y + PANEL_MOVE_MAX - pos.y);
        mBtmPanel.SetPosition(btmPos);
    }

    {
        Point pos = mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetPosition();
        if (pos.x != mHScrollTargetPos)
        {
            bool bMinus = pos.x > mHScrollTargetPos;
            s16 moveVal = Min(static_cast<s16>(Abs(mHScrollTargetPos - pos.x)), moveValMax);
            if (bMinus)
            {
                moveVal = static_cast<s16>(-moveVal);
            }
            pos.x += moveVal;
            mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->SetPosition(pos);

            Point headPos = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetPosition();
            headPos.x = static_cast<s16>(Min(pos.x - ITEM_BUTTON_CENTER_DISTANCE, 0));
            mItemsPanels[detail::ITEMS_PANEL_HEADER]->SetPosition(headPos);

            if (pos.x == mHScrollTargetPos)
            {
                SetScrollIndicatorPosX();
            }
        }
    }

    if (mTocIndScrollTargetPos != mBgPoss[0].x)
    {
        bool bMinus = mBgPoss[0].x > mTocIndScrollTargetPos;
        s16 moveVal = Min(static_cast<s16>(Abs(mTocIndScrollTargetPos - mBgPoss[0].x)), moveValMax);
        if (bMinus)
        {
            moveVal = static_cast<s16>(-moveVal);
        }
        mBgPoss[0].x += moveVal;
    }

    if (mItemsPanels[detail::ITEMS_PANEL_HEADER]->AnimateHeader(DrawHeaderTextCallback, mpManualToc))
    {
        if (mbDispItemBack)
        {
            // Update the animation. If it stops, display the header background.
            const bool bMoving = mItemsPanels[detail::ITEMS_PANEL_HEADER]->IsBusy();
            mManualItemBack->SetVisible(! bMoving);
            mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->SetVisible(! bMoving);
        }
    }

    static_cast<ManualButton*>(mHitItems[detail::BUTTON_EXIT])->Animate();
    static_cast<ManualButton*>(mHitItems[detail::BUTTON_DISP])->Animate();
    static_cast<ManualButton*>(mHitItems[detail::BUTTON_PREV])->Animate();
    static_cast<ManualButton*>(mHitItems[detail::BUTTON_NEXT])->Animate();
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::TransferBGDataToHW

  Description:  Transfers background data to the hardware.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::TransferBGDataToHW()
{
    // Display/hide the Contents background screen
    int visiblePlane = GX_GetVisiblePlane();
    if (IsVisible())
    {
        visiblePlane |= GX_PLANEMASK_BG0;
        visiblePlane |= GX_PLANEMASK_BG1;
        visiblePlane |= GX_PLANEMASK_BG2;
    }
    else
    {
        visiblePlane &= ~GX_PLANEMASK_BG0;
        visiblePlane &= ~GX_PLANEMASK_BG1;
        visiblePlane &= ~GX_PLANEMASK_BG2;
    }
    GX_SetVisiblePlane(visiblePlane);

    // Set the offset for the Contents background screen
    const int BG0HSize  = 512;
    const int BG2HSize  = 256;
    const int VSize     = 512;
    const int BG0HMask  = BG0HSize - 1; 
    const int BG2HMask  = BG2HSize - 1; 
    const int VMask     = VSize - 1;

    G2_SetBG0Offset((BG0HSize - mBgPoss[0].x) & BG0HMask, (VSize - mBgPoss[0].y) & VMask);
    G2_SetBG1Offset((BG0HSize - mBgPoss[1].x) & BG0HMask, (VSize - mBgPoss[1].y) & VMask);
    G2_SetBG2Offset((BG2HSize - mBgPoss[2].x) & BG2HMask, (VSize - mBgPoss[2].y) & VMask);
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::DrawHeaderButtonText

  Description:  Draws header button text.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::DrawHeaderButtonText()
{
    detail::ItemsPanel::DrawTextInfo drawTextInfoAry[detail::TOC_ITEM_DISP_MAX];

    u16 textNum = 0;
    s16 headIdx = 0;

    if (mpManualToc->GetHeaderNum() > 0)
    {
        headIdx = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetItemStartIdx();
        textNum = Min(static_cast<u16>(mpManualToc->GetHeaderNum() - headIdx), mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetItemAllocNum());
    }

    for (int i = 0; i < textNum; ++i)
    {
        const ManualHeader& manHeader = mpManualToc->GetHeaders()[headIdx + i];
        drawTextInfoAry[i].text = manHeader.GetHeaderText();
        if (drawTextInfoAry[i].text[0] == L'\0')
        {
            drawTextInfoAry[i].text = L"(---)";
        }
        drawTextInfoAry[i].bArrow = manHeader.GetChapterNum() > 0;
    }

    mItemsPanels[detail::ITEMS_PANEL_HEADER]->DrawHeaderButtonText(drawTextInfoAry, textNum, headIdx);
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::DrawChapterButtonText

  Description:  Draws section button text.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::DrawChapterButtonText()
{
    detail::ItemsPanel::DrawTextInfo drawTextInfoAry[detail::TOC_ITEM_DISP_MAX];

    u16 textNum = 0;
    s16 chapterIdx = 0;
    const ManualHeader* pManHeader = 0;

    const s16 headIdx = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetSelectedItemIdx();
    if (headIdx != -1)
    {
        chapterIdx = mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetItemStartIdx();
        pManHeader = &mpManualToc->GetHeaders()[headIdx];
        textNum = Min(static_cast<u16>(pManHeader->GetChapterNum() - chapterIdx), mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetItemAllocNum());
    }

    for (int i = 0; i < textNum; ++i)
    {
        drawTextInfoAry[i].text = pManHeader->GetChapters()[chapterIdx + i];
        if (drawTextInfoAry[i].text[0] == L'\0')
        {
            drawTextInfoAry[i].text = L"(---)";
        }
        drawTextInfoAry[i].bArrow = false;
    }

    mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->DrawChapterButtonText(drawTextInfoAry, textNum, chapterIdx);
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::InitChapterPage

  Description:  Initializes the section page.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::InitChapterPage()
{
    // Update the section button
    u16 chapterNum = 0;
    bool bSelectFirstChapter = false;
    const s16 headIdx = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetSelectedItemIdx();
    if (headIdx != -1)
    {
        const ManualHeader& manHeader = mpManualToc->GetHeaders()[headIdx];
        chapterNum = manHeader.GetChapterNum();

        // Is there no summary?
        if (0 < chapterNum && 0 == manHeader.GetHeaderFileName())
        {
            bSelectFirstChapter = true;
        }
    }

    mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->InitPage(chapterNum);
    DrawChapterButtonText();

    if (bSelectFirstChapter)
    {
        SetSelectedChapterIdx(0);   // Select the first section
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::SetNextPrevItemButtonState

  Description:  Updates the state of the Back and Next buttons.

  Arguments:    bEnablePrev:  TRUE to enable the Back button.
                bEnableNext:  TRUE to enable the Next button.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::SetNextPrevItemButtonState(
    bool    bEnablePrev,
    bool    bEnableNext
)
{
    mHitItems[detail::BUTTON_PREV]->SetEnabled(bEnablePrev);
    mHitItems[detail::BUTTON_NEXT]->SetEnabled(bEnableNext);
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::UpdateNextPrevItemButtonState

  Description:  Updates the current state of the Back and Next buttons.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::UpdateNextPrevItemButtonState()
{
    bool bPrev = false;
    bool bNext = false;

    const int headIdx = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetSelectedItemIdx();
    if (headIdx >= 0) // Has at least a header been selected?
    {
        const ManualHeader& manHeader = mpManualToc->GetHeaders()[headIdx];
        const int chapIdx = mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetSelectedItemIdx();
        bNext =     headIdx + 1 < mpManualToc->GetHeaderNum()
                ||  chapIdx + 1 < manHeader.GetChapterNum();
        bPrev =     headIdx > 0
                ||  chapIdx > GetStartChapterIndex(manHeader);
    }
    SetNextPrevItemButtonState(bPrev, bNext);
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::HitTestCtrlPanel

  Description:  Checks Touch Panel content and updates its state.

  Arguments:    pResult:  Pointer to a structure to store processing results.
                tpData:   Touch Panel data.

  Returns:      Returns TRUE if the Touch Panel was processed.
 *---------------------------------------------------------------------------*/
bool
ManualTocPanel::HitTestCtrlPanel(
    ManualTocTouchResult*   pResult,
    const TouchPanelData&   tpData
)
{
    bool bHandled = false;

    pResult->eventType = MANUALTOCEVENT_NONE;
    pResult->state     = 0;

    if (tpData.bTouchOn)
    {
        if (tpData.bTouchOnTrigger)
        {
            const s16 hitBtn = GetPointedButtonIdx(mHitItems, detail::UI_MAX, tpData.point);
            if (hitBtn != -1)
            {
                bHandled = true;

                if (mHitItems[hitBtn]->IsEnabled())
                {
                    mPointedUIIdx = hitBtn; // Record the button that was pointed at
                    mpSndMgr->startSe(TWL_MAN_SE_TOUCH_ON);
                }
                else
                {
                    if (hitBtn < detail::BUTTON_MAX)
                    {
                        mpSndMgr->startSe(TWL_MAN_SE_INVALID);
                    }
                }
            }
        }

        if (mPointedUIIdx != -1)
        {
            bHandled = true;

            // Is the button that was originally pressed currently being pointed at?
            mHitItems[mPointedUIIdx]->CheckHit(tpData.point);
        }
    }
    else if (tpData.bTouchOffTrigger)
    {
        if (mPointedUIIdx != -1)
        {
            const s16 btnIdx = mPointedUIIdx;
            mPointedUIIdx = -1; // clear the selection state

            bHandled = true;

            if (mHitItems[btnIdx]->IsPointed())
            {
                mHitItems[btnIdx]->SetPointed(false); // clear the selection state

                switch (btnIdx)
                {
                case detail::BUTTON_HELP:
                    pResult->eventType = MANUALTOCEVENT_HELPPAGESELECTED;
                    pResult->state = PressHelpButton() ?
                                        MANUALTOCHELPSTATE_SELECTED:
                                        MANUALTOCHELPSTATE_UNSELECTED;
                    break;
                case detail::BUTTON_EXIT:
                    pResult->eventType = MANUALTOCEVENT_EXIT;
                    PressExitButton();
                    break;
                case detail::BUTTON_DISP:
                    pResult->eventType = MANUALTOCEVENT_OPENCLOSE;
                    pResult->state = PressTocOpenButton() ? MANUALTOCDISPSTATE_OPENED: MANUALTOCDISPSTATE_CLOSED;
                    break;
                case detail::BUTTON_PREV:
                    pResult->eventType = MovePrevItem();
                    break;
                case detail::BUTTON_NEXT:
                    pResult->eventType = MoveNextItem();
                    break;
                case detail::HEADRETCOVER:
                    SetItemViewMode(detail::ITEMVIEWMODE_HEADER);
                    break;
                case detail::CHAPRETCOVER:
                    SetItemViewMode(detail::ITEMVIEWMODE_CHAPTER);
                    break;
                }
            }
        }
    }

    return bHandled;
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::MovePrevItem

  Description:  Moves to the previous item.

  Arguments:    None.

  Returns:      Returns the event type (MANUALTOCEVENT_*).
 *---------------------------------------------------------------------------*/
u16
ManualTocPanel::MovePrevItem()
{
    u16 eventType = MANUALTOCEVENT_NONE;

    if (IsOpened()) // This operation is not possible when the screen is open.
    {
        return eventType;
    }

    const int headIdx = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetSelectedItemIdx();
    if (headIdx >= 0) // Has at least a header been selected?
    {
        const ManualHeader& manHeader = mpManualToc->GetHeaders()[headIdx];
        const s16 chapIdx = mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetSelectedItemIdx();

        // Are there no sections in this header?
        // Or is the section summary displayed?
        // Either that, or this is the first section, without a section summary
        if ( manHeader.GetChapterNum() == 0
          || chapIdx == GetStartChapterIndex(manHeader)
        )
        {
            if (headIdx > 0)
            {
                const s16 prHeadIdx = static_cast<s16>(headIdx - 1);
                SetSelectedHeaderIdx(prHeadIdx);
                if (0 < mpManualToc->GetHeaders()[prHeadIdx].GetChapterNum())
                {
                    // Display the previous section
                    SetSelectedChapterIdx(static_cast<s16>(mpManualToc->GetHeaders()[prHeadIdx].GetChapterNum() - 1));
                }
                UpdateItemViewMode();
                eventType = MANUALTOCEVENT_CHAPTERSELECTCHANGED;
            }
        }
        else
        {
            // Display the previous section
            SetSelectedChapterIdx(static_cast<s16>(chapIdx - 1));
            eventType = MANUALTOCEVENT_CHAPTERSELECTCHANGED;
        }
    }

    if (eventType != MANUALTOCEVENT_NONE)
    {
        SetHelpButtonSelected(false);
        UpdateNextPrevItemButtonState();
        static_cast<ManualButton*>(mHitItems[detail::BUTTON_PREV])->SetPressReleased(true);
        mpSndMgr->startSe(TWL_MAN_SE_SELECT);
    }
    else
    {
        mpSndMgr->startSe(TWL_MAN_SE_INVALID);
    }

    return eventType;
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::MoveNextItem

  Description:  Moves to the next item.

  Arguments:    None.

  Returns:      Returns the event type (MANUALTOCEVENT_*).
 *---------------------------------------------------------------------------*/
u16
ManualTocPanel::MoveNextItem()
{
    u16 eventType = MANUALTOCEVENT_NONE;

    if (IsOpened()) // This operation is not possible when the screen is open.
    {
        return eventType;
    }

    const int headIdx = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetSelectedItemIdx();

    // Are there no headers selected?
    // Or does this header have no sections?
    // Or is the last section displayed?
    if ( headIdx == -1
      || mpManualToc->GetHeaders()[headIdx].GetChapterNum() == 0
      || mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetSelectedItemIdx() + 1 >= mpManualToc->GetHeaders()[headIdx].GetChapterNum()
    )
    {
        const s16 nxHeadIdx = static_cast<s16>(headIdx + 1);
        if (nxHeadIdx < mpManualToc->GetHeaderNum())
        {
            SetSelectedHeaderIdx(nxHeadIdx);
            UpdateItemViewMode();
            eventType = MANUALTOCEVENT_HEADERSELECTCHANGED;
        }
    }
    else
    {
        SetSelectedChapterIdx(static_cast<s16>(mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetSelectedItemIdx() + 1));
        eventType = MANUALTOCEVENT_CHAPTERSELECTCHANGED;
    }

    if (eventType != MANUALTOCEVENT_NONE)
    {
        SetHelpButtonSelected(false);
        UpdateNextPrevItemButtonState();
        static_cast<ManualButton*>(mHitItems[detail::BUTTON_NEXT])->SetPressReleased(true);
        mpSndMgr->startSe(TWL_MAN_SE_SELECT);
    }
    else
    {
        mpSndMgr->startSe(TWL_MAN_SE_INVALID);
    }

    return eventType;
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::PressHelpButton

  Description:  Runs processing when the Help button has been pressed.

  Arguments:    None.

  Returns:      Returns TRUE when the Help button is selected.
 *---------------------------------------------------------------------------*/
bool
ManualTocPanel::PressHelpButton()
{
    SetHelpButtonSelected(! mbHelpSelected);
    mpSndMgr->startSe(TWL_MAN_SE_SELECT);
    return mbHelpSelected;
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::PressHelpButton

  Description:  Runs processing when the Help button has been pressed.

  Arguments:    pbHelpSelected:  Pointer to a variable to store the Help button's selection state.

  Returns:      Returns TRUE when processing was run for the pressed Help button.
 *---------------------------------------------------------------------------*/
bool
ManualTocPanel::PressHelpButton(bool* pbHelpSelected)
{
    if (IsBusy())
    {
        return false;
    }

    *pbHelpSelected = PressHelpButton();
    return true;
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::PressTocOpenButton

  Description:  Runs processing when the button to open and close the Contents page was pressed.

  Arguments:    None.

  Returns:      Returns TRUE when the Contents page is open.
 *---------------------------------------------------------------------------*/
bool
ManualTocPanel::PressTocOpenButton()
{
    SetOpened(! IsOpened());
    static_cast<ManualButton*>(mHitItems[detail::BUTTON_DISP])->SetPressReleased(true);
    mpSndMgr->startSe(TWL_MAN_SE_LAYOUT_CHG);
    return IsOpened();
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::PressExitButton

  Description:  Runs processing when the Exit button has been pressed.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::PressExitButton()
{
    static_cast<ManualButton*>(mHitItems[detail::BUTTON_EXIT])->SetPressReleased(true);
    mpSndMgr->startSe(TWL_MAN_SE_CLOSE);
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::IsBusy

  Description:  Determines if a long-running operation is still in progress.

  Arguments:    None.

  Returns:      Returns TRUE when a long-running operation is still in progress.
 *---------------------------------------------------------------------------*/
bool
ManualTocPanel::IsBusy() const
{
    s16 pos = 0;
    return (0 < (pos = mWholePanel.GetPosition().y) && pos < PANEL_MOVE_MAX)
        || mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetPosition().x != mHScrollTargetPos
        || mItemsPanels[detail::ITEMS_PANEL_HEADER]->IsBusy();

}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::SetItemViewMode

  Description:  Sets the item display state.

  Arguments:    mode:  Item display state.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::SetItemViewMode(u16 mode)
{
    mItemViewMode = mode;

    // Set the scroll position depending on the mode.
    // Also, use Visible to enable or disable detection of pressing mHeadRetCover and mChapRetCover.
    // 
    switch (mode)
    {
    case detail::ITEMVIEWMODE_HEADER:
        mHScrollTargetPos = ITEM_BUTTON_CENTER_DISTANCE;
        mHeadRetCover->SetVisible(false);
        mChapRetCover->SetVisible(true);
        break;
    case detail::ITEMVIEWMODE_HEADERONLY:
        mHScrollTargetPos = ITEM_BUTTON_CENTER_DISTANCE;
        mHeadRetCover->SetVisible(false);
        mChapRetCover->SetVisible(false);
        break;
    case detail::ITEMVIEWMODE_CHAPTER:
        mHScrollTargetPos = 0;
        mHeadRetCover->SetVisible(true);
        mChapRetCover->SetVisible(false);
        break;
    }

    if (mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->GetPosition().x != mHScrollTargetPos)
    {
        mpSndMgr->startSe(TWL_MAN_SE_SUBMENU_OPEN);
    }
    else    // when the header or section has not changed
    {
        SetScrollIndicatorPosX();
    }
}

void
ManualTocPanel::SetScrollIndicatorPosX()
{
    switch (mItemViewMode)
    {
    case detail::ITEMVIEWMODE_HEADER:
        mTocIndScrollTargetPos = TOC_SCROLL_INDICATOR_ORG_POS_X - TOC_SCROLL_INDICATOR_MOVE_X;
        break;
    case detail::ITEMVIEWMODE_HEADERONLY:
        mTocIndScrollTargetPos = TOC_SCROLL_INDICATOR_ORG_POS_X;
        break;
    case detail::ITEMVIEWMODE_CHAPTER:
        mTocIndScrollTargetPos = TOC_SCROLL_INDICATOR_ORG_POS_X + TOC_SCROLL_INDICATOR_MOVE_X;
        break;
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::UpdateItemViewMode

  Description:  Updates the item display state.

  Arguments:    bSelectPage: TRUE when the header item display state is changed by switching the page of header items.
                             

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::UpdateItemViewMode(bool bSelectPage)
{
    u16 itemViewMode = detail::ITEMVIEWMODE_HEADERONLY;
    const int headerIdx = mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetSelectedItemIdx();
    mbDispItemBack = false;
    if (headerIdx >= 0)
    {
        // Move the image shown below header item buttons so that it is below the currently selected item.
        const int itemIdx = headerIdx - mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetItemStartIdx();
        if ( (0 <= itemIdx && itemIdx < mItemsPanels[detail::ITEMS_PANEL_HEADER]->GetItemDispMax())      // Is the selected header visible?
          && 0 < mpManualToc->GetHeaders()[headerIdx].GetChapterNum()   // Does the header have sections?
        )
        {
            Point pos = mManualItemBack->GetPosition();
            pos.y = static_cast<s16>(itemIdx * ITEM_BUTTON_LINEHEIGHT + HEADER_BUTTON_BASE_POS_Y - CHAPTER_BUTTON_BASE_POS_Y);
            mManualItemBack->SetPosition(pos);
            mManualItemBack->SetAnimFrame(mpObjManager, HEADERITEMBACKSTATE_NORMAL);
            mbDispItemBack = true;

            itemViewMode = bSelectPage ? detail::ITEMVIEWMODE_HEADER: detail::ITEMVIEWMODE_CHAPTER;
        }
    }
    mManualItemBack->SetVisible(mbDispItemBack);
    mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->SetVisible(mbDispItemBack);
    SetItemViewMode(itemViewMode);
}

/*---------------------------------------------------------------------------*
  Name:         ManualTocPanel::SetHelpButtonSelected

  Description:  Sets the selection state of the Help button.

  Arguments:    bSelected: TRUE to select it.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualTocPanel::SetHelpButtonSelected(bool bSelected)
{
    mbHelpSelected = bSelected;
    static_cast<ManualButton*>(mHitItems[detail::BUTTON_HELP])->SetChecked(bSelected);
    if (mbHelpSelected)
    {
        mItemsPanels[detail::ITEMS_PANEL_HEADER]->UpdateItemButtonState(false);
        mItemsPanels[detail::ITEMS_PANEL_HEADER]->SetFocused(false);
        mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->UpdateItemButtonState(false);
        mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->SetFocused(false);
    }
    else
    {
        mItemsPanels[detail::ITEMS_PANEL_HEADER]->SetFocused(true);
        mItemsPanels[detail::ITEMS_PANEL_HEADER]->UpdateItemButtonState(true);
        mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->SetFocused(true);
        mItemsPanels[detail::ITEMS_PANEL_CHAPTER]->UpdateItemButtonState(true);
    }
}

}   // namespace m2d
}   // namespace ntmv
