Files
GTASource/game/frontend/UIPaginator.h
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

380 lines
12 KiB
C++

#ifndef __UI_PAGINATOR_H__
#define __UI_PAGINATOR_H__
#include "atl/array.h"
#include "atl/pool.h"
#include "rline/clan/rlclancommon.h"
#include "Frontend/PauseMenuData.h" // for scrollflags
#include "Frontend/CMenuBase.h" // MenuScreenId
#include "data/callback.h"
//////////////////////////////////////////////////////////////////////////
// A class for paginating data from a large superset
// into smaller, bite-sized chunks which can then, in turn
// be displayed in a much smaller UI list
// Example: Leaderboard entries.
// Data set: 100,000+ rows, obviously too big for memory
// Data page: 96 rows, nice and good for fetches
// UI Window: 16 rows at a time
// LIMITATION: Your data page size should be a factor of your UI window,
// because cross-datapage wrapping is haaaaaaard
//////////////////////////////////////////////////////////////////////////
/*
// To create your own pagination class, you need to do the following four (4) things:
// 1. Define a derived DataPage, having at least these functions
class MyDataPage : public UIDataPageBase
{
public:
DECLARE_UI_DATA_PAGE(MyDataPage, 30); // or whatever data size you want
// whatever information each row needs to collect its data
bool SetData(UniqueDataPerColumn data)
{
// using GetPagedIndex(), we can determine what data to collect
TakeColumnDataAndGetOurPortionOfIt(data, GetPagedIndex());
return true;
}
// see UIDataPageBase for more information on these
virtual bool IsReady() const { return m_Data.Valid(); }
virtual int GetSize() const { return m_Data.GetSize(); }
virtual bool FillScaleformEntry( LocalDataIndex iItemIndex, int iUIIndex, MenuScreenId ColumnId, DataIndex iUniqueId, int iColumn )
{
// take iItemIndex, determine what we need from it, and pass it on
if( IsIndexGoodToShow(iItemIndex) )
{
const char* pszShowThis = m_Data[iItemIndex];
CPauseMenu::SET_DATA_SLOT(iColumn, iUIIndex, ColumnId + PREF_OPTIONS_THRESHOLD, iUniqueId, OPTION_DISPLAY_STYLE_SLIDER, false, true, pszShowThis);
return true;
}
return false;
}
private:
UniqueData m_Data;
};
// 2. In your .cpp, call:
IMPLEMENT_UI_DATA_PAGE(MyDataPage);
// 3. Create your paginator class like so:
class MyDataPaginator : public UIPaginator<MyDataPage>
{
public:
// Called on creation to set your instance apart from the others
void Init(UniqueDataPerColumn myData, int VisibleItemsPerPage, MenuScreenId ColumnId, int iColumnIndex)
{
// IMPORTANT: set our data FIRST before calling InitInternal
m_MyData = myData;
InitInternal(VisibleItemsPerPage, ColumnId, iColumnIndex);
}
// imparts the UniqueDataPerColumn onto a given datapage
virtual bool SetDataOnPage(UIDataPageBase* thisPage)
{
MyDataPage* pPage = verify_cast<MyDataPage*>(thisPage);
if( pPage )
{
return pPage->SetData(m_MyData);
}
return false;
}
private:
UniqueDataPerColumn m_MyData;
};
// 4. HOOK IT ALL UP
// In your MenuPage, call these (assuming MyDataPaginator m_PaginatorInstance exists in the class definition)
MenuPage::MenuPage()
{
m_PaginatorInstance.Init(SomeData, MaxPerUI, MENU_UNIQUE_ID_WHATEVER, WhichColumn);
}
void MenuPage::UpdateInput(s32 eInput)
{
// checks if LT/RT has been pressed
if( m_PaginatorInstance.UpdateInput(eInput) )
return; // handled!
}
void MenuPage::Populate()
{
// assuming the first item in our menu is the paginator
// the false is because on populate, our menus tend not to have focus
// and as such, shouldn't redraw
m_PaginatorInstance.UpdateItemSelection(0,false);
}
void MenuPage::LayoutChanged( MenuScreenId, MenuScreenId iNewLayout, s32 iUniqueId )
{
// if we're navigating the menu
if( m_PaginatorInstance.GetMenuScreenId() == iNewLayout )
{
// the true indicates that this menu is being navigated
// and such, will need its highlight redrawn afterwards
m_PaginatorInstance.UpdateItemSelection(iUniqueId, true);
}
else
{
// we're navigating some OTHER screen, so lose focus on that menu
m_PaginatorInstace.LoseFocus();
}
}
void MenuPage::Update()
{
// necessary if your data takes time to process (such as net requests)
m_PaginatorInstance.Update();
}
*/
//////////////////////////////////////////////////////////////////////////
namespace UIPage {
typedef int PageIndex; // human-notioned pages, [0,infinity)
typedef int DataIndex; // item index from 0 to however large the dataset is; is NOT the internal
typedef int LocalDataIndex; // index of data in local, datapage-space
typedef int LocalPageIndex; // internal array index of a datapage
};
using namespace UIPage;
// forward declarations
class UIPaginatorBase;
class CMenuScreen;
class UIDataPageBase
{
public:
UIDataPageBase(const UIPaginatorBase* const pOwner, PageIndex pageIndex);
virtual ~UIDataPageBase() {};
// REQUIRE DEFINITION IN THE DERIVED CLASSES
// <BEGIN READER EXERCISE>
// returns true when the data is ready (downloaded, processed, whatever)
virtual bool IsReady() const = 0;
virtual bool HasFailed() const {return false;} // SHOULD probably be pure virtual, but I don't want to have to implement it everywhere
virtual int GetResultCode() const { return 0; } // same as above; should return a code
virtual void Update(){};
// returns the actual number of entries
virtual int GetSize() const = 0; // for this page
virtual int GetTotalSize() const = 0; // for ALL pages
// Fill out scaleform, via CPauseMenu::SET_DATA_SLOT
// iItemIndex - Index of data, [0, GetSize() )
// iUIIndex - Row index of UI [0, UIWindowSize )
// ColumnId - MenuIdentifier of what to call this row
// iUniqueId - Total index in dataset [0, n)
// iColumn - which column to SET_DATA_SLOT on.
virtual bool FillScaleformEntry( LocalDataIndex iItemIndex, int iUIIndex, MenuScreenId ColumnId, DataIndex iUniqueId, int iColumn, bool bIsUpdate ) = 0;
virtual bool FillEmptyScaleformSlots( LocalDataIndex UNUSED_PARAM(iItemIndex), int UNUSED_PARAM(iSlotIndex), MenuScreenId UNUSED_PARAM(menuId),
DataIndex UNUSED_PARAM(playerIndex), int UNUSED_PARAM(iColumn) ) {return true;}
// <END READER EXERCISE>
// REGULAR FUNCTIONS requiring no interaction
virtual int GetMaxSizeVirtual() const = 0;
virtual bool CanHighlightSlot() const {return true;}
virtual void SetSelectedItem(int iColumn, int iSlotIndex, DataIndex playerIndex);
public:
void LoseFocus();
virtual void FillScaleformBase(int iColumn, MenuScreenId ColumnId, DataIndex iStartingIndex, int iMaxPerPage, bool bHasFocus);
virtual void UpdateIndex(int iColumn, MenuScreenId ColumnId, DataIndex item);
PageIndex GetPagedIndex() const { return m_PagedIndex; }
DataIndex GetLastShownIndex() const { return m_LastShownIndex; }
DataIndex GetLowestEntry() const { return m_PagedIndex*GetMaxSizeVirtual(); }
int GetLastShownItemCount() const { return m_iLastShownItemCount; }
protected:
const UIPaginatorBase* const m_pPageOwner;
private:
PageIndex m_PagedIndex;
DataIndex m_LastShownIndex;
int m_iLastShownItemCount;
};
class UIPaginatorBase : public datBase
{
public:
UIPaginatorBase(const CMenuScreen* const pOwner);
virtual ~UIPaginatorBase() {};
static const int DEFAULT_MAX_PAGES = 5;
virtual void UpdateItemSelection(DataIndex iNewIndex, bool bHasFocus);
virtual void PageUp();
virtual void PageDown();
virtual void ItemUp();
virtual void ItemDown();
void LoseFocus();
virtual bool UpdateInput(s32 eInput);
virtual void Update(bool bAllowedToDraw = true);
bool IsReady() const { return GetActivePage() && GetActivePage()->IsReady(); }
bool HasFailed() const { return GetActivePage() && GetActivePage()->HasFailed(); }
int GetResultCode() const { return GetActivePage() ? GetActivePage()->GetResultCode() : -1; }
bool HasFocus() const { return m_bGainedFocus; }
int GetTotalSize() const { return (GetActivePage() && GetActivePage()->IsReady()) ? GetActivePage()->GetTotalSize() : 0; }
DataIndex GetCurrentHighlight() const {return m_iLastHighlight; }
virtual void MoveToIndex(s32 UNUSED_PARAM(iIndex)) {}
void UpdateIndex(DataIndex iItem){ if(GetActivePage()) GetActivePage()->UpdateIndex(m_iColumnIndex, m_ColumnId, iItem); }
MenuScreenId GetMenuScreenId() const { return m_ColumnId; }
const UIDataPageBase* GetActivePage() const { return GetPagePtr(0); }
UIDataPageBase* GetActivePage() { return GetPagePtr(0); }
const CMenuScreen* const GetOwner() const;
void SetDelayedOwner(MenuScreenId newOwner) { m_DelayedOwner = newOwner; m_pOwner = NULL; }
int GetNumVisibleItemsPerPage() const {return m_VisibleItemsPerPage;}
void SetShowHideCB(const datCallback& callWhenHidden) { m_ShowHideCB = callWhenHidden; }
void SetNoResultsCB(const datCallback& callWhenNoResults) { m_HandleNoResultsCB = callWhenNoResults; }
// derived class must call shutdownbase AND delete its own pool
virtual void Shutdown() = 0;
// blows away any pages that have been created
void ResetPages();
protected:
// must be called by derived class!
void InitInternal( int VisibleItemsPerPage, MenuScreenId ColumnId, int iColumnIndex, u8 MaxCachedPages = DEFAULT_MAX_PAGES, int iBusyColumnIndex = -1 );
// must be set up on derived class
virtual bool SetDataOnPage( UIDataPageBase* setForThisPage) = 0;
// these are handled by the template.
virtual void InitPools(u8 MaxCachedPages) = 0;
virtual void SetCurrentPool() = 0;
virtual bool IsPoolFull() const = 0;
virtual int GetDataPageMaxSize() const = 0;
virtual UIDataPageBase* CreatePage(PageIndex iPage) = 0;
virtual bool HasBeenInitialized() const = 0;
void ShutdownBase();
UIDataPageBase* FindPageContaining(DataIndex iItem);
const UIDataPageBase* FindPageContaining(DataIndex iItem) const
{ return const_cast<const UIDataPageBase*>(const_cast<UIPaginatorBase*>(this)->FindPageContaining(iItem)); }
private:
UIDataPageBase* RequestPage(PageIndex iPageIndex );
UIDataPageBase* RequestNewPage(PageIndex iPageIndex);
// performs the necessary index->pool entry lookup
const UIDataPageBase* GetPagePtr(LocalPageIndex index) const { return index < m_ActivePages.GetCount() ? m_ActivePages[index] : NULL;}
UIDataPageBase* GetPagePtr(LocalPageIndex index) { return index < m_ActivePages.GetCount() ? m_ActivePages[index] : NULL;}
void SetActivePage(UIDataPageBase* pNewPage);
bool ShowBusy( bool bHow );
bool ShowNoResults( bool bNoZults );
protected:
// protected for use in the template
typedef rage::atArray<UIDataPageBase*> PageWindow;
PageWindow m_ActivePages;
mutable const CMenuScreen* m_pOwner;
DataIndex m_iLastHighlight;
DataIndex m_iQueuedHighlight;
int m_VisibleItemsPerPage;
int m_iColumnIndex;
private:
MenuScreenId m_ColumnId;
MenuScreenId m_DelayedOwner;
datCallback m_ShowHideCB;
datCallback m_HandleNoResultsCB;
int m_iBusyColumnIndex;
bool m_bShowedBusy;
bool m_bGainedFocus;
bool m_bNoResults;
};
template <class _Type>
class UIPaginator : public UIPaginatorBase
{
private:
typedef rage::atPool<_Type> DataPool;
DataPool* m_PagePool;
virtual _Type* CreatePage(PageIndex iPage ) { return rage_new _Type(this, iPage); }
public:
UIPaginator(const CMenuScreen* const pOwner) : UIPaginatorBase(pOwner), m_PagePool(NULL) {};
virtual ~UIPaginator() { if( m_PagePool ) delete m_PagePool; }
virtual bool HasBeenInitialized() const { return m_PagePool != NULL; }
virtual void SetCurrentPool() { _Type::SetCurrentPool(m_PagePool); }
virtual bool IsPoolFull() const { return m_PagePool && m_PagePool->IsFull(); }
virtual int GetDataPageMaxSize() const { return _Type::GetMaxSize(); }
virtual void InitPools( u8 MaxCachedPages )
{
if( !HasBeenInitialized() )
{
m_PagePool = rage_new DataPool;
m_PagePool->Init( MaxCachedPages );
}
}
virtual void Shutdown()
{
UIPaginatorBase::ShutdownBase();
if( HasBeenInitialized() )
{
delete m_PagePool;
m_PagePool = NULL;
}
}
};
#define DECLARE_UI_DATA_PAGE(ClassName, MaxDataSize)\
public:\
static const int DATA_PAGE_SIZE = MaxDataSize;\
virtual int GetMaxSizeVirtual() const { return DATA_PAGE_SIZE; }\
static int GetMaxSize() { return DATA_PAGE_SIZE; }\
DECLARE_CLASS_NEW_DELETE(ClassName);
#define IMPLEMENT_UI_DATA_PAGE(ClassName) \
IMPLEMENT_CLASS_NEW_DELETE(ClassName, #ClassName);
#endif // __UI_PAGINATOR_H__