Files
GTASource/game/frontend/page_deck/layout/PageLayoutBase.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

466 lines
14 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// FILE : PageLayoutBase.cpp
// PURPOSE : Base for scene layout primitives
//
// AUTHOR : james.strain
// STARTED : January 2021
//
/////////////////////////////////////////////////////////////////////////////////
#include "PageLayoutBase.h"
#include "frontend/page_deck/uiPageConfig.h"
#if UI_PAGE_DECK_ENABLED
#include "PageLayoutBase_parser.h"
// rage
#include "vector/geometry.h"
// framework
#include "fwutil/xmacro.h"
// game
#include "frontend/ui_channel.h"
#include "frontend/page_deck/layout/PageLayoutItem.h"
#include "frontend/page_deck/PageItemCategoryBase.h"
#include "frontend/VideoEditor/ui/InstructionalButtons.h"
#include "system/control_mapping.h"
#include "text/TextFile.h"
FWUI_DEFINE_TYPE( CPageLayoutBase, 0x57E69C05 );
void CPageLayoutBase::RegisterItem( CPageLayoutItemBase& item, CPageLayoutItemParams const& params )
{
if( uiVerifyf( !IsItemRegistered( item ), "Attempting to register the same item twice?" ) )
{
m_registeredItems.PushAndGrow( &item );
m_registeredItemParams.PushAndGrow( params );
}
}
bool CPageLayoutBase::IsItemRegistered( CPageLayoutItemBase const& item ) const
{
return GetIndex(item) >= 0;
}
void CPageLayoutBase::UnregisterItem( CPageLayoutItemBase const& item )
{
int const c_idx = GetIndex(item);
if( c_idx != INDEX_NONE )
{
m_registeredItems.Delete( c_idx );
m_registeredItemParams.Delete( c_idx );
}
}
void CPageLayoutBase::UnregisterAll()
{
m_registeredItems.ResetCount();
m_registeredItemParams.ResetCount();
}
void CPageLayoutBase::RecalculateLayout( Vec2f_In position, Vec2f_In extents )
{
m_position = position;
RecalculateLayoutInternal( extents );
NotifyOfLayoutUpdate();
}
void CPageLayoutBase::NotifyOfLayoutUpdate()
{
int const c_itemCount = m_registeredItems.GetCount();
for( int index = 0; index < c_itemCount; ++index )
{
CPageLayoutItemBase& item = *m_registeredItems[index];
CPageLayoutItemParams const& c_params = m_registeredItemParams[index];
PositionItem( item, c_params );
}
}
CPageLayoutItemParams CPageLayoutBase::GenerateParams( CPageItemBase const& pageItem ) const
{
return GenerateParamsDerived( pageItem );
}
char const * const CPageLayoutBase::GetSymbol( CPageItemBase const& pageItem ) const
{
return GetSymbolDerived( pageItem );
}
void CPageLayoutBase::SetDefaultFocus()
{
int const c_defaultFocusIdx = GetDefaultItemFocusIndex();
rage::fwuiNavigationResult const c_result = GetNavDetailsForItem( c_defaultFocusIdx );
if( uiVerifyf( c_result.IsValid(), "Unable to set default focus to item %d", c_defaultFocusIdx ) )
{
CPageLayoutItemBase * const focusItem = GetItemByIndex( c_defaultFocusIdx );
if( uiVerifyf( focusItem, "Unable to find item %d despite earlier calls telling us to focus it", c_defaultFocusIdx ) )
{
OnNavigatedToItem( nullptr, focusItem );
}
m_navigationContext.AssignFocusItem( c_result );
}
}
void CPageLayoutBase::InvalidateFocus()
{
rage::fwuiNavigationResult const c_oldFocus = m_navigationContext.GetFocusedItemDetails();
m_navigationContext.ClearFocus();
if( c_oldFocus.IsValid() )
{
CPageLayoutItemBase * const oldFocusItem = GetItemByIndex( c_oldFocus.GetItemIndex() );
OnNavigatedToItem( oldFocusItem, nullptr );
}
}
void CPageLayoutBase::RefreshFocusVisuals()
{
rage::fwuiNavigationResult const c_currentFocus = m_navigationContext.GetFocusedItemDetails();
if( c_currentFocus.IsValid() )
{
CPageLayoutItemBase * const currentFocusItem = GetItemByIndex( c_currentFocus.GetItemIndex() );
OnNavigatedToItem( nullptr, currentFocusItem );
}
}
void CPageLayoutBase::ClearFocusVisuals()
{
rage::fwuiNavigationResult const c_currentFocus = m_navigationContext.GetFocusedItemDetails();
if( c_currentFocus.IsValid() )
{
CPageLayoutItemBase * const currentFocusItem = GetItemByIndex( c_currentFocus.GetItemIndex() );
OnNavigatedToItem( currentFocusItem, nullptr );
}
}
char const * CPageLayoutBase::GetFocusTooltip() const
{
char const * result = nullptr;
rage::fwuiNavigationResult const c_currentFocus = m_navigationContext.GetFocusedItemDetails();
if( c_currentFocus.IsValid() )
{
CPageLayoutItemBase const * const c_currentFocusItem = GetItemByIndex( c_currentFocus.GetItemIndex() );
result = c_currentFocusItem ? c_currentFocusItem->GetTooltop() : nullptr;
}
return result;
}
void CPageLayoutBase::UpdateInstructionalButtons( atHashString const acceptLabelOverride ) const
{
rage::fwuiNavigationResult const c_currentFocus = m_navigationContext.GetFocusedItemDetails();
if( c_currentFocus.IsValid() )
{
CPageLayoutItemBase const * const c_focusItem = GetItemByIndex( c_currentFocus.GetItemIndex() );
if( c_focusItem && c_focusItem->IsEnabled() )
{
char const * const c_label = uiPageConfig::GetLabel( ATSTRINGHASH( "IB_SELECT", 0xD7ED7F0C ), acceptLabelOverride );
CVideoEditorInstructionalButtons::AddButton( INPUT_FRONTEND_ACCEPT, c_label, true );
}
}
}
void CPageLayoutBase::PlayEnterAnimation()
{
PlayEnterAnimationDerived();
}
fwuiInput::eHandlerResult CPageLayoutBase::HandleDigitalNavigation( int const deltaX, int const deltaY )
{
fwuiInput::eHandlerResult result( fwuiInput::ACTION_NOT_HANDLED );
rage::fwuiNavigationResult const c_oldFocus = m_navigationContext.GetFocusedItemDetails();
rage::fwuiNavigationConfig const& c_navConfig = m_navigationContext.GetConfig();
rage::fwuiNavigationParams const c_navParams( c_navConfig, c_oldFocus, deltaX, deltaY );
rage::fwuiNavigationResult const c_newFocus = HandleDigitalNavigationDerived( c_navParams );
if( m_navigationContext.AssignFocusItem( c_newFocus ) )
{
CPageLayoutItemBase * const oldFocusItem = GetItemByIndex( c_oldFocus.GetItemIndex() );
CPageLayoutItemBase * const newFocusItem = GetItemByIndex( c_newFocus.GetItemIndex() );
OnNavigatedToItem( oldFocusItem, newFocusItem );
result = fwuiInput::ACTION_HANDLED;
}
return result;
}
fwuiInput::eHandlerResult CPageLayoutBase::HandleInputAction( eFRONTEND_INPUT const inputAction, IPageMessageHandler& messageHandler )
{
fwuiInput::eHandlerResult result( fwuiInput::ACTION_NOT_HANDLED );
rage::fwuiNavigationResult const c_currentFocus = m_navigationContext.GetFocusedItemDetails();
if( c_currentFocus.IsValid() )
{
CPageLayoutItemBase * const currentFocusItem = GetItemByIndex( c_currentFocus.GetItemIndex() );
if( currentFocusItem )
{
result = OnActionItem( *currentFocusItem, inputAction, messageHandler );
}
}
return result;
}
#if RSG_BANK
void CPageLayoutBase::DebugRender() const
{
DebugRenderDerived();
int const c_count = m_registeredItems.GetCount();
for( int index = 0; index < c_count; ++index )
{
CPageLayoutItemBase const * const c_item = m_registeredItems[index];
c_item->DebugRender();
}
}
#endif // RSG_BANK
CPageLayoutItemBase * CPageLayoutBase::GetItemByIndex( int const index )
{
CPageLayoutItemBase * const result = index >= 0 && index < m_registeredItems.GetCount() ? m_registeredItems[index] : nullptr;
return result;
}
CPageLayoutItemBase const * CPageLayoutBase::GetItemByIndex( int const index ) const
{
CPageLayoutItemBase const * const c_result = index >= 0 && index < m_registeredItems.GetCount() ? m_registeredItems[index] : nullptr;
return c_result;
}
CPageLayoutItemParams const * CPageLayoutBase::GetItemParamsByIndex( int const index ) const
{
CPageLayoutItemParams const * const c_result = index >= 0 && index < m_registeredItemParams.GetCount() ? &m_registeredItemParams[index] : nullptr;
return c_result;
}
int CPageLayoutBase::GetIndex( CPageLayoutItemBase const& item ) const
{
int result = INDEX_NONE;
int const c_count = m_registeredItems.GetCount();
for( int index = 0; result == INDEX_NONE && index < c_count; ++index )
{
result = m_registeredItems[index] == &item ? index : result;
}
return result;
}
int CPageLayoutBase::GetClosestIntersectingItemIdx( Vec2f_In p0, Vec2f_In p1, Vec2f_In p2, int const indexToSkip ) const
{
int result = INDEX_NONE;
float oldAngle = LARGE_FLOAT;
float oldLength = LARGE_FLOAT;
int const c_childCount = GetItemCount();
Vec2f const c_sideAShim = p1 - p0;
Vector2 const c_sideA( c_sideAShim.GetX(), c_sideAShim.GetY() );
Vec2f const c_sideBShim = p2 - p0;
Vector2 const c_sideB( c_sideBShim.GetX(), c_sideBShim.GetY() );
for( int index = 0; index < c_childCount; ++index )
{
// We may be calling this function to figure out child-to-child navigation and will want to skip the child we are starting from
// (which naturally will always intersect the triangle)
if( index == indexToSkip )
continue;
Vec2f itemPos, itemSize;
GetItemPositionAndSize( index, itemPos, itemSize );
float const c_x0 = itemPos.GetX();
float const c_y0 = itemPos.GetY();
float const c_x1 = itemPos.GetX() + itemSize.GetX();
float const c_y1 = itemPos.GetY() + itemSize.GetY();
bool const c_intersects = geom2D::Test2DTriVsAlignedRect( p0.GetX(), p0.GetY(), p1.GetX(), p1.GetY(), p2.GetX(), p2.GetY(),
c_x0, c_y0, c_x1, c_y1 );
if( c_intersects )
{
Vec2f const c_childCenter = itemPos + ( itemSize * 0.5f );
Vec2f const c_diffRay = c_childCenter - p0;
// Shimming from RDR code, where Vec2f and Vector2 were merged
Vector2 const c_diffRayShim( c_diffRay.GetX(), c_diffRay.GetY() );
if( c_diffRayShim.IsNonZero() )
{
float const c_length = c_diffRayShim.Mag2();
float const c_angleA = c_diffRayShim.Angle( c_sideA );
float const c_angleB = c_diffRayShim.Angle( c_sideB );
float const c_angle = fabs(c_angleB-c_angleA);
// Pick the closest length, and favor a centered element
if( c_length <= oldLength && c_angle <= oldAngle )
{
oldAngle = c_angle;
oldLength = c_length;
result = index;
}
}
else
{
continue;
}
}
}
return result;
}
void CPageLayoutBase::GetItemPositionAndSize( int const index, Vec2f_InOut out_pos, Vec2f_InOut out_size ) const
{
GetItemPositionAndSizeDerived( index, out_pos, out_size );
}
char const * const CPageLayoutBase::GetSymbolDerived( CPageItemBase const& pageItem ) const
{
return pageItem.GetSymbol();
}
void CPageLayoutBase::ForEachLayoutItemDerived(PerLayoutItemLambda action)
{
int const c_itemCount = GetItemCount();
for( int index = 0; index < c_itemCount; ++index )
{
CPageLayoutItemBase * const layoutItem = m_registeredItems[index];
if( layoutItem )
{
action( *layoutItem );
}
}
}
void CPageLayoutBase::ForEachLayoutItemDerived(PerLayoutItemConstLambda action) const
{
int const c_itemCount = GetItemCount();
for( int index = 0; index < c_itemCount; ++index )
{
CPageLayoutItemBase const * const c_layoutItem = m_registeredItems[index];
if( c_layoutItem )
{
action( *c_layoutItem );
}
}
}
void CPageLayoutBase::PlayEnterAnimationDerived()
{
int itemIndex = 0;
auto itemFunc = [&itemIndex]( CPageLayoutItemBase& layoutItem )
{
layoutItem.PlayRevealAnimation( itemIndex );
++itemIndex;
};
ForEachLayoutItemDerived( itemFunc );
}
int CPageLayoutBase::GetDefaultItemFocusIndex() const
{
int result = INDEX_NONE;
int const c_count = m_registeredItems.GetCount();
for( int index = 0; result == INDEX_NONE && index < c_count; ++index )
{
result = m_registeredItems[index]->IsInteractive() ? index : result;
}
return result;
}
rage::fwuiNavigationResult CPageLayoutBase::GetNavDetailsForItem( int const index ) const
{
int const c_itemCount = GetItemCount();
CPageLayoutItemParams const * const c_params = c_itemCount > 0 && index < c_itemCount ? GetItemParamsByIndex( index ) : nullptr;
if( c_params )
{
return rage::fwuiNavigationResult( c_params->GetX(), c_params->GetY(), index );
}
else
{
return rage::fwuiNavigationResult();
}
}
CPageLayoutItemParams CPageLayoutBase::GenerateParamsDerived( CPageItemBase const& UNUSED_PARAM(pageItem) ) const
{
return CPageLayoutItemParams();
}
void CPageLayoutBase::OnNavigatedToItem( CPageLayoutItemBase* const oldItem, CPageLayoutItemBase * const newItem )
{
if( oldItem )
{
oldItem->OnFocusLost();
}
if( newItem )
{
newItem->OnFocusGained();
}
}
fwuiInput::eHandlerResult CPageLayoutBase::OnActionItem( CPageLayoutItemBase& item, eFRONTEND_INPUT const inputAction, IPageMessageHandler& messageHandler )
{
fwuiInput::eHandlerResult result( fwuiInput::ACTION_NOT_HANDLED );
if( inputAction == FRONTEND_INPUT_ACCEPT )
{
if( item.IsEnabled() )
{
item.OnSelected( messageHandler );
result = fwuiInput::ACTION_HANDLED;
}
else
{
result = fwuiInput::ACTION_IGNORED;
}
}
return result;
}
rage::fwuiNavigationResult CPageLayoutBase::HandleDigitalNavigationDerived( rage::fwuiNavigationParams const& navParams )
{
int const c_delta = navParams.GetDigitalDeltaY();
bool const c_isWrapping = navParams.GetNavConfig().IsWrappingY();
int const c_existingIndex = navParams.GetPreviousResult().GetItemIndex();
int const c_newIndex = HandleDigitalNavigationSimple( c_delta, c_isWrapping, c_existingIndex );
return rage::fwuiNavigationResult( INDEX_NONE, c_newIndex, c_newIndex );
}
int CPageLayoutBase::HandleDigitalNavigationSimple( int const delta, bool const isWrapping, int const existingIndex )
{
int result = INDEX_NONE;
int const c_itemCount = GetItemCount();
if( c_itemCount > 0 )
{
result = existingIndex + delta;
if( result < 0 )
{
result = isWrapping ? c_itemCount - 1 : 0;
}
if( result >= c_itemCount )
{
result = isWrapping ? 0 : c_itemCount - 1;
}
}
return result;
}
#endif // UI_PAGE_DECK_ENABLED