2023-06-30 15:15:49 +00:00
# include "Common/System/OSD.h"
2023-06-19 21:23:50 +00:00
# include "Common/System/Request.h"
2023-06-16 14:29:44 +00:00
# include "Common/UI/View.h"
# include "Common/UI/ViewGroup.h"
2023-06-17 20:22:59 +00:00
# include "Common/UI/Context.h"
2023-06-16 14:29:44 +00:00
# include "Common/Data/Text/I18n.h"
2023-06-18 13:13:16 +00:00
# include "Common/UI/IconCache.h"
2023-06-15 11:40:37 +00:00
2023-06-19 21:23:50 +00:00
# include "Core/Config.h"
2023-07-02 10:00:13 +00:00
# include "Core/RetroAchievements.h"
2023-06-19 21:23:50 +00:00
2023-07-16 10:15:44 +00:00
# include "UI/RetroAchievementScreens.h"
# include "UI/BackgroundAudio.h"
2023-07-16 15:34:56 +00:00
# include "UI/OnScreenDisplay.h"
2023-07-16 10:15:44 +00:00
2023-07-10 17:26:41 +00:00
static inline const char * DeNull ( const char * ptr ) {
return ptr ? ptr : " " ;
}
2023-07-16 10:15:44 +00:00
// Compound view, creating a FileChooserChoice inside.
class AudioFileChooser : public UI : : LinearLayout {
public :
AudioFileChooser ( std : : string * value , const std : : string & title , UI : : UISound sound , UI : : LayoutParams * layoutParams = nullptr ) ;
UI : : UISound sound_ ;
} ;
static constexpr UI : : Size ITEM_HEIGHT = 64.f ;
AudioFileChooser : : AudioFileChooser ( std : : string * value , const std : : string & title , UI : : UISound sound , UI : : LayoutParams * layoutParams ) : UI : : LinearLayout ( UI : : ORIENT_HORIZONTAL , layoutParams ) , sound_ ( sound ) {
using namespace UI ;
SetSpacing ( 2.0f ) ;
if ( ! layoutParams ) {
layoutParams_ - > width = FILL_PARENT ;
layoutParams_ - > height = ITEM_HEIGHT ;
}
Add ( new FileChooserChoice ( value , title , BrowseFileType : : SOUND_EFFECT , new LinearLayoutParams ( 1.0f ) ) ) - > OnChange . Add ( [ = ] ( UI : : EventParams & e ) {
// TODO: Check the file format here.
// Need to forward the event out.
std : : string path = e . s ;
Sample * sample = Sample : : Load ( path ) ;
if ( sample ) {
g_BackgroundAudio . SFX ( ) . UpdateSample ( sound , sample ) ;
} else {
if ( ! sample ) {
auto au = GetI18NCategory ( I18NCat : : AUDIO ) ;
g_OSD . Show ( OSDType : : MESSAGE_WARNING , au - > T ( " Audio file format not supported. Must be 16-bit WAV. " ) ) ;
}
value - > clear ( ) ;
}
return UI : : EVENT_DONE ;
} ) ;
Add ( new Choice ( ImageID ( " I_ARROW_RIGHT " ) , new LinearLayoutParams ( ITEM_HEIGHT , ITEM_HEIGHT ) ) ) - > OnClick . Add ( [ = ] ( UI : : EventParams & ) {
g_BackgroundAudio . SFX ( ) . Play ( sound_ , 0.6f ) ;
return UI : : EVENT_DONE ;
} ) ;
Add ( new Choice ( ImageID ( " I_TRASHCAN " ) , new LinearLayoutParams ( ITEM_HEIGHT , ITEM_HEIGHT ) ) ) - > OnClick . Add ( [ = ] ( UI : : EventParams & ) {
2023-07-16 13:04:21 +00:00
g_BackgroundAudio . SFX ( ) . UpdateSample ( sound , nullptr ) ;
2023-07-16 10:15:44 +00:00
value - > clear ( ) ;
return UI : : EVENT_DONE ;
} ) ;
}
2023-06-21 12:32:10 +00:00
void RetroAchievementsListScreen : : CreateTabs ( ) {
2023-06-19 21:47:23 +00:00
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-15 11:40:37 +00:00
2023-06-27 09:12:25 +00:00
UI : : LinearLayout * achievements = AddTab ( " Achievements " , ac - > T ( " Achievements " ) ) ;
achievements - > SetSpacing ( 5.0f ) ;
CreateAchievementsTab ( achievements ) ;
2023-06-17 09:41:05 +00:00
2023-06-27 21:31:15 +00:00
UI : : LinearLayout * leaderboards = AddTab ( " Leaderboards " , ac - > T ( " Leaderboards " ) ) ;
leaderboards - > SetSpacing ( 5.0f ) ;
CreateLeaderboardsTab ( leaderboards ) ;
2023-06-17 09:41:05 +00:00
2023-06-27 09:12:25 +00:00
# ifdef _DEBUG
CreateStatisticsTab ( AddTab ( " AchievementsStatistics " , ac - > T ( " Statistics " ) ) ) ;
# endif
}
void RetroAchievementsListScreen : : CreateAchievementsTab ( UI : : ViewGroup * achievements ) {
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
using namespace UI ;
2023-06-19 21:47:23 +00:00
2023-07-03 20:17:07 +00:00
int filter = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE ;
if ( Achievements : : UnofficialEnabled ( ) ) {
filter = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL ;
}
2023-06-27 21:31:15 +00:00
rc_client_achievement_list_t * list = rc_client_create_achievement_list ( Achievements : : GetClient ( ) ,
2023-07-03 20:17:07 +00:00
filter , RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE ) ;
2023-06-27 21:31:15 +00:00
std : : vector < const rc_client_achievement_t * > unlockedAchievements ;
std : : vector < const rc_client_achievement_t * > lockedAchievements ;
std : : vector < const rc_client_achievement_t * > otherAchievements ;
for ( uint32_t i = 0 ; i < list - > num_buckets ; i + + ) {
const rc_client_achievement_bucket_t & bucket = list - > buckets [ i ] ;
for ( uint32_t j = 0 ; j < bucket . num_achievements ; j + + ) {
switch ( bucket . bucket_type ) {
case RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED :
lockedAchievements . push_back ( bucket . achievements [ j ] ) ;
break ;
case RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED :
unlockedAchievements . push_back ( bucket . achievements [ j ] ) ;
break ;
default :
otherAchievements . push_back ( bucket . achievements [ j ] ) ;
break ;
}
}
}
2023-06-17 09:41:05 +00:00
2023-06-21 12:32:10 +00:00
achievements - > Add ( new ItemHeader ( ac - > T ( " Achievements " ) ) ) ;
2023-06-27 12:58:39 +00:00
achievements - > Add ( new GameAchievementSummaryView ( ) ) ;
2023-06-17 09:41:05 +00:00
2023-07-16 20:12:06 +00:00
if ( Achievements : : EncoreModeActive ( ) ) {
achievements - > Add ( new NoticeView ( NoticeLevel : : WARN , ac - > T ( " In Encore mode - unlock state may not be accurate " ) , " " ) ) ;
}
2023-07-16 16:23:43 +00:00
CollapsibleSection * unlocked = new CollapsibleSection ( StringFromFormat ( " %s (%d) " , ac - > T ( " Unlocked achievements " ) , ( int ) unlockedAchievements . size ( ) ) ) ;
unlocked - > SetSpacing ( 2.0f ) ;
2023-06-27 09:12:25 +00:00
for ( auto & achievement : unlockedAchievements ) {
2023-07-16 15:34:56 +00:00
unlocked - > Add ( new AchievementView ( achievement ) ) ;
2023-06-19 21:47:23 +00:00
}
2023-07-16 15:34:56 +00:00
achievements - > Add ( unlocked ) ;
2023-07-16 16:23:43 +00:00
CollapsibleSection * locked = new CollapsibleSection ( StringFromFormat ( " %s (%d) " , ac - > T ( " Locked achievements " ) , ( int ) lockedAchievements . size ( ) ) ) ;
unlocked - > SetSpacing ( 2.0f ) ;
2023-06-27 09:12:25 +00:00
for ( auto & achievement : lockedAchievements ) {
2023-07-16 15:34:56 +00:00
locked - > Add ( new AchievementView ( achievement ) ) ;
2023-06-27 21:31:15 +00:00
}
2023-07-16 15:34:56 +00:00
achievements - > Add ( locked ) ;
2023-07-16 16:23:43 +00:00
CollapsibleSection * other = new CollapsibleSection ( StringFromFormat ( " %s (%d) " , ac - > T ( " Other achievements " ) , ( int ) otherAchievements . size ( ) ) ) ;
unlocked - > SetSpacing ( 2.0f ) ;
2023-06-27 21:31:15 +00:00
for ( auto & achievement : otherAchievements ) {
2023-07-16 15:34:56 +00:00
other - > Add ( new AchievementView ( achievement ) ) ;
2023-06-17 09:41:05 +00:00
}
2023-07-16 15:34:56 +00:00
achievements - > Add ( other ) ;
2023-06-15 11:40:37 +00:00
}
2023-06-16 14:29:44 +00:00
2023-06-27 09:12:25 +00:00
void RetroAchievementsListScreen : : CreateLeaderboardsTab ( UI : : ViewGroup * viewGroup ) {
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
using namespace UI ;
2023-06-27 12:58:39 +00:00
viewGroup - > Add ( new GameAchievementSummaryView ( ) ) ;
2023-06-27 09:12:25 +00:00
viewGroup - > Add ( new ItemHeader ( ac - > T ( " Leaderboards " ) ) ) ;
2023-06-27 21:31:15 +00:00
std : : vector < rc_client_leaderboard_t * > leaderboards ;
rc_client_leaderboard_list_t * list = rc_client_create_leaderboard_list ( Achievements : : GetClient ( ) , RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE ) ;
for ( uint32_t i = 0 ; i < list - > num_buckets ; i + + ) {
const rc_client_leaderboard_bucket_t & bucket = list - > buckets [ i ] ;
for ( uint32_t j = 0 ; j < bucket . num_leaderboards ; j + + ) {
leaderboards . push_back ( bucket . leaderboards [ j ] ) ;
}
}
2023-06-27 09:12:25 +00:00
for ( auto & leaderboard : leaderboards ) {
2023-06-27 21:31:15 +00:00
int leaderboardID = leaderboard - > id ;
viewGroup - > Add ( new LeaderboardSummaryView ( leaderboard ) ) - > OnClick . Add ( [ = ] ( UI : : EventParams & e ) - > UI : : EventReturn {
screenManager ( ) - > push ( new RetroAchievementsLeaderboardScreen ( gamePath_ , leaderboardID ) ) ;
return UI : : EVENT_DONE ;
} ) ;
2023-06-27 09:12:25 +00:00
}
}
void RetroAchievementsListScreen : : CreateStatisticsTab ( UI : : ViewGroup * viewGroup ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
using namespace UI ;
Achievements : : Statistics stats = Achievements : : GetStatistics ( ) ;
viewGroup - > Add ( new ItemHeader ( ac - > T ( " Statistics " ) ) ) ;
viewGroup - > Add ( new InfoItem ( ac - > T ( " Bad memory accesses " ) , StringFromFormat ( " %d " , stats . badMemoryAccessCount ) ) ) ;
}
2023-06-27 21:31:15 +00:00
RetroAchievementsLeaderboardScreen : : ~ RetroAchievementsLeaderboardScreen ( ) {
if ( pendingAsyncCall_ ) {
rc_client_abort_async ( Achievements : : GetClient ( ) , pendingAsyncCall_ ) ;
}
Poll ( ) ; // Gets rid of pendingEntryList_.
if ( entryList_ ) {
rc_client_destroy_leaderboard_entry_list ( entryList_ ) ;
}
}
RetroAchievementsLeaderboardScreen : : RetroAchievementsLeaderboardScreen ( const Path & gamePath , int leaderboardID )
: TabbedUIDialogScreenWithGameBackground ( gamePath ) , leaderboardID_ ( leaderboardID ) {
rc_client_begin_fetch_leaderboard_entries ( Achievements : : GetClient ( ) , leaderboardID_ , 0 , 20 , [ ] ( int result , const char * error_message , rc_client_leaderboard_entry_list_t * list , rc_client_t * client , void * userdata ) {
if ( result ! = RC_OK ) {
g_OSD . Show ( OSDType : : MESSAGE_ERROR , error_message , 10.0f ) ;
return ;
}
RetroAchievementsLeaderboardScreen * thiz = ( RetroAchievementsLeaderboardScreen * ) userdata ;
thiz - > pendingEntryList_ = list ;
thiz - > pendingAsyncCall_ = nullptr ;
} , this ) ;
}
2023-06-27 09:12:25 +00:00
void RetroAchievementsLeaderboardScreen : : CreateTabs ( ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-27 21:31:15 +00:00
const rc_client_leaderboard_t * leaderboard = rc_client_get_leaderboard_info ( Achievements : : GetClient ( ) , leaderboardID_ ) ;
2023-06-27 09:12:25 +00:00
2023-06-27 21:31:15 +00:00
using namespace UI ;
UI : : LinearLayout * layout = AddTab ( " AchievementsLeaderboard " , leaderboard - > title ) ;
2023-06-27 09:12:25 +00:00
layout - > Add ( new TextView ( leaderboard - > description ) ) ;
layout - > Add ( new ItemHeader ( ac - > T ( " Leaderboard " ) ) ) ;
2023-06-27 21:31:15 +00:00
if ( entryList_ ) {
for ( uint32_t i = 0 ; i < entryList_ - > num_entries ; i + + ) {
bool is_self = ( i = = entryList_ - > user_index ) ;
// Should highlight somehow.
const rc_client_leaderboard_entry_t & entry = entryList_ - > entries [ i ] ;
2023-07-02 15:12:46 +00:00
char buffer [ 512 ] ;
rc_client_leaderboard_entry_get_user_image_url ( & entryList_ - > entries [ i ] , buffer , sizeof ( buffer ) ) ;
2023-06-27 21:31:15 +00:00
// Can also show entry.submitted, which is a time_t. And maybe highlight recent ones?
2023-07-02 15:12:46 +00:00
layout - > Add ( new LeaderboardEntryView ( & entryList_ - > entries [ i ] , is_self ) ) ;
2023-06-27 21:31:15 +00:00
}
2023-06-27 09:12:25 +00:00
}
}
void RetroAchievementsLeaderboardScreen : : Poll ( ) {
2023-06-27 21:31:15 +00:00
if ( pendingEntryList_ ) {
if ( entryList_ ) {
rc_client_destroy_leaderboard_entry_list ( entryList_ ) ;
}
entryList_ = pendingEntryList_ ;
pendingEntryList_ = nullptr ;
2023-06-27 09:12:25 +00:00
RecreateViews ( ) ;
}
}
void RetroAchievementsLeaderboardScreen : : update ( ) {
TabbedUIDialogScreenWithGameBackground : : update ( ) ;
Poll ( ) ;
}
2023-06-27 12:58:39 +00:00
RetroAchievementsSettingsScreen : : ~ RetroAchievementsSettingsScreen ( ) { }
2023-06-16 14:29:44 +00:00
void RetroAchievementsSettingsScreen : : CreateTabs ( ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-23 20:08:52 +00:00
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
2023-07-11 08:30:50 +00:00
auto sy = GetI18NCategory ( I18NCat : : SYSTEM ) ;
2023-06-16 14:29:44 +00:00
using namespace UI ;
2023-06-27 07:47:35 +00:00
CreateAccountTab ( AddTab ( " AchievementsAccount " , ac - > T ( " Account " ) ) ) ;
2023-07-16 13:04:21 +00:00
if ( System_GetPropertyBool ( SYSPROP_HAS_FILE_BROWSER ) ) {
// Don't bother creating this tab if we don't have a file browser.
CreateCustomizeTab ( AddTab ( " AchievementsCustomize " , ac - > T ( " Customize " ) ) ) ;
}
2023-07-11 08:30:50 +00:00
CreateDeveloperToolsTab ( AddTab ( " AchievementsDeveloperTools " , sy - > T ( " Developer Tools " ) ) ) ;
2023-06-16 14:29:44 +00:00
}
void RetroAchievementsSettingsScreen : : sendMessage ( const char * message , const char * value ) {
TabbedUIDialogScreenWithGameBackground : : sendMessage ( message , value ) ;
if ( ! strcmp ( message , " achievements_loginstatechange " ) ) {
RecreateViews ( ) ;
}
}
void RetroAchievementsSettingsScreen : : CreateAccountTab ( UI : : ViewGroup * viewGroup ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-21 13:45:07 +00:00
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
2023-06-16 14:29:44 +00:00
using namespace UI ;
2023-07-01 10:31:46 +00:00
if ( ! g_Config . bAchievementsEnable ) {
viewGroup - > Add ( new TextView ( ac - > T ( " Achievements are disabled " ) ) ) ;
} else if ( Achievements : : IsLoggedIn ( ) ) {
2023-06-27 21:31:15 +00:00
const rc_client_user_t * info = rc_client_get_user_info ( Achievements : : GetClient ( ) ) ;
// In the future, RetroAchievements will support display names. Prepare for that.
if ( strcmp ( info - > display_name , info - > username ) ! = 0 ) {
viewGroup - > Add ( new InfoItem ( ac - > T ( " Name " ) , info - > display_name ) ) ;
}
viewGroup - > Add ( new InfoItem ( ac - > T ( " Username " ) , info - > username ) ) ;
// viewGroup->Add(new InfoItem(ac->T("Unread messages"), info.numUnreadMessages));
2023-06-23 20:08:52 +00:00
viewGroup - > Add ( new Choice ( di - > T ( " Log out " ) ) ) - > OnClick . Add ( [ = ] ( UI : : EventParams & ) - > UI : : EventReturn {
2023-06-16 14:29:44 +00:00
Achievements : : Logout ( ) ;
return UI : : EVENT_DONE ;
} ) ;
} else {
2023-07-21 23:30:20 +00:00
std : : string errorMessage ;
if ( Achievements : : LoginProblems ( & errorMessage ) ) {
viewGroup - > Add ( new NoticeView ( NoticeLevel : : WARN , ac - > T ( " Failed logging in to RetroAchievements " ) , errorMessage ) ) ;
if ( Achievements : : HasToken ( ) ) {
viewGroup - > Add ( new Choice ( di - > T ( " Retry " ) ) ) - > OnClick . Add ( [ = ] ( UI : : EventParams & ) - > UI : : EventReturn {
Achievements : : TryLoginByToken ( ) ;
return UI : : EVENT_DONE ;
} ) ;
}
viewGroup - > Add ( new Choice ( di - > T ( " Log out " ) ) ) - > OnClick . Add ( [ = ] ( UI : : EventParams & ) - > UI : : EventReturn {
Achievements : : Logout ( ) ;
return UI : : EVENT_DONE ;
} ) ;
} else if ( System_GetPropertyBool ( SYSPROP_HAS_LOGIN_DIALOG ) ) {
2023-06-23 20:08:52 +00:00
viewGroup - > Add ( new Choice ( ac - > T ( " Log in " ) ) ) - > OnClick . Add ( [ = ] ( UI : : EventParams & ) - > UI : : EventReturn {
2023-06-21 13:45:07 +00:00
System_AskUsernamePassword ( ac - > T ( " Log in " ) , [ ] ( const std : : string & value , int ) {
std : : vector < std : : string > parts ;
SplitString ( value , ' \n ' , parts ) ;
if ( parts . size ( ) = = 2 & & ! parts [ 0 ] . empty ( ) & & ! parts [ 1 ] . empty ( ) ) {
Achievements : : LoginAsync ( parts [ 0 ] . c_str ( ) , parts [ 1 ] . c_str ( ) ) ;
}
2023-06-27 09:12:25 +00:00
} ) ;
2023-06-21 13:45:07 +00:00
return UI : : EVENT_DONE ;
} ) ;
} else {
// Hack up a temporary quick login-form-ish-thing
2023-06-23 20:08:52 +00:00
viewGroup - > Add ( new PopupTextInputChoice ( & username_ , di - > T ( " Username " ) , " " , 128 , screenManager ( ) ) ) ;
2023-07-12 17:21:08 +00:00
viewGroup - > Add ( new PopupTextInputChoice ( & password_ , di - > T ( " Password " ) , " " , 128 , screenManager ( ) ) ) - > SetPasswordDisplay ( ) ;
Choice * loginButton = viewGroup - > Add ( new Choice ( di - > T ( " Log in " ) ) ) ;
loginButton - > OnClick . Add ( [ = ] ( UI : : EventParams & ) - > UI : : EventReturn {
2023-06-21 13:45:07 +00:00
if ( ! username_ . empty ( ) & & ! password_ . empty ( ) ) {
Achievements : : LoginAsync ( username_ . c_str ( ) , password_ . c_str ( ) ) ;
2023-07-16 06:57:08 +00:00
memset ( & password_ [ 0 ] , 0 , password_ . size ( ) ) ;
2023-07-12 17:21:08 +00:00
password_ . clear ( ) ;
2023-06-19 21:23:50 +00:00
}
2023-06-21 13:45:07 +00:00
return UI : : EVENT_DONE ;
2023-06-19 21:23:50 +00:00
} ) ;
2023-07-12 17:21:08 +00:00
loginButton - > SetEnabledFunc ( [ & ] ( ) {
return ! username_ . empty ( ) & & ! password_ . empty ( ) ;
} ) ;
2023-06-21 13:45:07 +00:00
}
2023-06-19 21:23:50 +00:00
viewGroup - > Add ( new Choice ( ac - > T ( " Register on www.retroachievements.org " ) ) ) - > OnClick . Add ( [ & ] ( UI : : EventParams & ) - > UI : : EventReturn {
System_LaunchUrl ( LaunchUrlType : : BROWSER_URL , " https://retroachievements.org/createaccount.php " ) ;
2023-06-16 14:29:44 +00:00
return UI : : EVENT_DONE ;
} ) ;
}
2023-06-21 13:45:07 +00:00
2023-07-11 08:30:50 +00:00
using namespace UI ;
viewGroup - > Add ( new ItemHeader ( di - > T ( " Settings " ) ) ) ;
viewGroup - > Add ( new CheckBox ( & g_Config . bAchievementsEnable , ac - > T ( " Achievements enabled " ) ) ) - > OnClick . Add ( [ & ] ( UI : : EventParams & e ) - > UI : : EventReturn {
Achievements : : UpdateSettings ( ) ;
RecreateViews ( ) ;
return UI : : EVENT_DONE ;
} ) ;
viewGroup - > Add ( new CheckBox ( & g_Config . bAchievementsChallengeMode , ac - > T ( " Challenge Mode (no savestates) " ) ) ) - > SetEnabledPtr ( & g_Config . bAchievementsEnable ) ;
viewGroup - > Add ( new CheckBox ( & g_Config . bAchievementsEncoreMode , ac - > T ( " Encore Mode " ) ) ) - > SetEnabledPtr ( & g_Config . bAchievementsEnable ) ;
2023-07-12 17:18:56 +00:00
viewGroup - > Add ( new CheckBox ( & g_Config . bAchievementsSoundEffects , ac - > T ( " Sound Effects " ) ) ) - > SetEnabledPtr ( & g_Config . bAchievementsEnable ) ; // not yet implemented
2023-07-11 08:30:50 +00:00
2023-06-21 13:45:07 +00:00
viewGroup - > Add ( new ItemHeader ( di - > T ( " Links " ) ) ) ;
viewGroup - > Add ( new Choice ( ac - > T ( " RetroAchievements website " ) ) ) - > OnClick . Add ( [ & ] ( UI : : EventParams & ) - > UI : : EventReturn {
2023-06-19 21:23:50 +00:00
System_LaunchUrl ( LaunchUrlType : : BROWSER_URL , " https://www.retroachievements.org/ " ) ;
return UI : : EVENT_DONE ;
} ) ;
2023-06-21 13:45:07 +00:00
viewGroup - > Add ( new Choice ( ac - > T ( " How to use RetroAchievements " ) ) ) - > OnClick . Add ( [ & ] ( UI : : EventParams & ) - > UI : : EventReturn {
System_LaunchUrl ( LaunchUrlType : : BROWSER_URL , " https://www.ppsspp.org/docs/reference/retro-achievements " ) ;
return UI : : EVENT_DONE ;
} ) ;
2023-06-19 21:23:50 +00:00
}
2023-07-16 10:15:44 +00:00
void RetroAchievementsSettingsScreen : : CreateCustomizeTab ( UI : : ViewGroup * viewGroup ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
using namespace UI ;
viewGroup - > Add ( new ItemHeader ( ac - > T ( " Sound effects " ) ) ) ;
2023-07-16 14:39:04 +00:00
viewGroup - > Add ( new AudioFileChooser ( & g_Config . sAchievementsUnlockAudioFile , ac - > T ( " Achievement unlocked " ) , UISound : : ACHIEVEMENT_UNLOCKED ) ) ;
viewGroup - > Add ( new AudioFileChooser ( & g_Config . sAchievementsLeaderboardSubmitAudioFile , ac - > T ( " Leaderboard score submission " ) , UISound : : LEADERBOARD_SUBMITTED ) ) ;
2023-07-16 10:15:44 +00:00
}
2023-07-11 08:30:50 +00:00
void RetroAchievementsSettingsScreen : : CreateDeveloperToolsTab ( UI : : ViewGroup * viewGroup ) {
2023-06-19 21:23:50 +00:00
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
using namespace UI ;
viewGroup - > Add ( new ItemHeader ( ac - > T ( " Settings " ) ) ) ;
2023-07-11 08:30:50 +00:00
viewGroup - > Add ( new CheckBox ( & g_Config . bAchievementsUnofficial , ac - > T ( " Unofficial achievements " ) ) ) - > SetEnabledPtr ( & g_Config . bAchievementsEnable ) ;
2023-07-01 10:31:46 +00:00
viewGroup - > Add ( new CheckBox ( & g_Config . bAchievementsLogBadMemReads , ac - > T ( " Log bad memory accesses " ) ) ) - > SetEnabledPtr ( & g_Config . bAchievementsEnable ) ;
2023-06-16 14:29:44 +00:00
}
2023-06-17 20:22:59 +00:00
2023-06-27 21:31:15 +00:00
void MeasureAchievement ( const UIContext & dc , const rc_client_achievement_t * achievement , AchievementRenderStyle style , float * w , float * h ) {
2023-06-17 20:22:59 +00:00
* w = 0.0f ;
2023-06-27 21:31:15 +00:00
switch ( style ) {
case AchievementRenderStyle : : PROGRESS_INDICATOR :
2023-07-03 12:39:49 +00:00
dc . MeasureText ( dc . theme - > uiFont , 1.0f , 1.0f , achievement - > measured_progress , w , h ) ;
2023-07-03 22:49:17 +00:00
* w + = 44.0f + 4.0f * 3.0f ;
* h = 44.0f ;
2023-06-27 21:31:15 +00:00
break ;
2023-07-03 12:39:49 +00:00
case AchievementRenderStyle : : CHALLENGE_INDICATOR :
// ONLY the icon.
* w = 60.0f ;
* h = 60.0f ;
break ;
2023-06-27 21:31:15 +00:00
default :
* h = 72.0f ;
break ;
}
2023-06-17 20:22:59 +00:00
}
2023-06-27 12:58:39 +00:00
void MeasureGameAchievementSummary ( const UIContext & dc , float * w , float * h ) {
2023-07-03 22:49:17 +00:00
std : : string description = Achievements : : GetGameAchievementSummary ( ) ;
float tw , th ;
dc . MeasureText ( dc . theme - > uiFont , 1.0f , 1.0f , " Wg " , & tw , & th ) ;
dc . MeasureText ( dc . theme - > uiFont , 0.66f , 0.66f , description . c_str ( ) , w , h ) ;
2023-07-16 19:58:27 +00:00
* h + = 8.0f + th ;
2023-07-03 22:49:17 +00:00
* w + = 8.0f ;
2023-06-19 21:47:23 +00:00
}
2023-06-27 21:31:15 +00:00
void MeasureLeaderboardSummary ( const UIContext & dc , const rc_client_leaderboard_t * leaderboard , float * w , float * h ) {
2023-06-27 09:12:25 +00:00
* w = 0.0f ;
* h = 72.0f ;
}
2023-07-02 15:12:46 +00:00
void MeasureLeaderboardEntry ( const UIContext & dc , const rc_client_leaderboard_entry_t * entry , float * w , float * h ) {
* w = 0.0f ;
* h = 72.0f ;
}
2023-06-17 20:22:59 +00:00
// Graphical
2023-07-16 20:07:26 +00:00
void RenderAchievement ( UIContext & dc , const rc_client_achievement_t * achievement , AchievementRenderStyle style , const Bounds & bounds , float alpha , float startTime , float time_s , bool hasFocus ) {
2023-06-17 20:22:59 +00:00
using namespace UI ;
2023-06-26 08:13:18 +00:00
UI : : Drawable background = UI : : Drawable ( dc . theme - > backgroundColor ) ;
2023-07-03 22:49:17 +00:00
2023-07-16 20:07:26 +00:00
if ( hasFocus ) {
background = dc . theme - > itemFocusedStyle . background ;
}
2023-07-03 22:49:17 +00:00
// Set some alpha, if displayed in list.
if ( style = = AchievementRenderStyle : : LISTED ) {
background . color = colorAlpha ( background . color , 0.6f ) ;
}
2023-07-16 20:07:26 +00:00
if ( ! achievement - > unlocked & & ! hasFocus ) {
2023-06-27 21:31:15 +00:00
// Make the background color gray.
// TODO: Different colors in challenge mode, or even in the "re-take achievements" mode when we add that?
2023-07-03 22:49:17 +00:00
background . color = ( background . color & 0xFF000000 ) | 0x706060 ;
2023-06-21 12:32:10 +00:00
}
2023-07-03 22:49:17 +00:00
int iconState = achievement - > state ;
background . color = alphaMul ( background . color , alpha ) ;
uint32_t fgColor = alphaMul ( dc . theme - > itemStyle . fgColor , alpha ) ;
2023-06-17 20:22:59 +00:00
2023-06-21 12:32:10 +00:00
if ( style = = AchievementRenderStyle : : UNLOCKED ) {
float mixWhite = pow ( Clamp ( ( float ) ( 1.0f - ( time_s - startTime ) ) , 0.0f , 1.0f ) , 3.0f ) ;
background . color = colorBlend ( 0xFFE0FFFF , background . color , mixWhite ) ;
}
2023-07-03 12:39:49 +00:00
float padding = 4.0f ;
float iconSpace = bounds . h - padding * 2.0f ;
2023-06-20 07:18:47 +00:00
dc . Flush ( ) ;
2023-07-03 12:39:49 +00:00
dc . RebindTexture ( ) ;
2023-07-12 17:21:08 +00:00
dc . PushScissor ( bounds ) ;
2023-06-18 13:13:16 +00:00
2023-06-17 20:22:59 +00:00
dc . Begin ( ) ;
2023-07-03 12:39:49 +00:00
2023-07-03 22:49:17 +00:00
if ( style ! = AchievementRenderStyle : : LISTED ) {
dc . DrawRectDropShadow ( bounds , 12.0f , 0.7f * alpha ) ;
}
2023-06-17 20:22:59 +00:00
dc . FillRect ( background , bounds ) ;
2023-07-03 12:39:49 +00:00
dc . Flush ( ) ;
dc . Begin ( ) ;
2023-06-21 12:32:10 +00:00
dc . SetFontStyle ( dc . theme - > uiFont ) ;
2023-07-03 12:39:49 +00:00
char temp [ 512 ] ;
2023-06-17 20:22:59 +00:00
2023-07-03 12:39:49 +00:00
switch ( style ) {
case AchievementRenderStyle : : LISTED :
case AchievementRenderStyle : : UNLOCKED :
{
dc . SetFontScale ( 1.0f , 1.0f ) ;
dc . DrawTextRect ( achievement - > title , bounds . Inset ( iconSpace + 12.0f , 2.0f , padding , padding ) , fgColor , ALIGN_TOPLEFT ) ;
2023-06-17 20:22:59 +00:00
2023-07-03 12:39:49 +00:00
dc . SetFontScale ( 0.66f , 0.66f ) ;
2023-07-17 10:02:56 +00:00
dc . DrawTextRectSqueeze ( DeNull ( achievement - > description ) , bounds . Inset ( iconSpace + 12.0f , 39.0f , padding , padding ) , fgColor , ALIGN_TOPLEFT ) ;
2023-06-27 21:31:15 +00:00
2023-07-03 22:49:17 +00:00
if ( style = = AchievementRenderStyle : : LISTED & & strlen ( achievement - > measured_progress ) > 0 ) {
dc . SetFontScale ( 1.0f , 1.0f ) ;
dc . DrawTextRect ( achievement - > measured_progress , bounds . Inset ( iconSpace + 12.0f , padding , padding + 100.0f , padding ) , fgColor , ALIGN_VCENTER | ALIGN_RIGHT ) ;
}
2023-07-03 12:39:49 +00:00
// TODO: Draw measured_progress / measured_percent in a cute way
snprintf ( temp , sizeof ( temp ) , " %d " , achievement - > points ) ;
2023-06-17 20:22:59 +00:00
2023-07-03 12:39:49 +00:00
dc . SetFontScale ( 1.5f , 1.5f ) ;
dc . DrawTextRect ( temp , bounds . Expand ( - 5.0f , - 5.0f ) , fgColor , ALIGN_RIGHT | ALIGN_VCENTER ) ;
2023-06-17 20:22:59 +00:00
2023-07-03 12:39:49 +00:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
dc . Flush ( ) ;
break ;
}
case AchievementRenderStyle : : PROGRESS_INDICATOR :
// TODO: Also render a progress bar.
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-07-03 22:49:17 +00:00
dc . DrawTextRect ( achievement - > measured_progress , bounds . Inset ( iconSpace + padding * 2.0f , padding , padding , padding ) , fgColor , ALIGN_LEFT | ALIGN_VCENTER ) ;
// Show the unlocked icon.
iconState = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED ;
2023-07-03 12:39:49 +00:00
break ;
case AchievementRenderStyle : : CHALLENGE_INDICATOR :
2023-07-03 22:49:17 +00:00
// Nothing but the icon, unlocked.
iconState = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED ;
2023-07-03 12:39:49 +00:00
break ;
}
2023-06-18 13:13:16 +00:00
2023-06-27 21:31:15 +00:00
// Download and display the image.
2023-07-03 22:49:17 +00:00
char cacheKey [ 256 ] ;
snprintf ( cacheKey , sizeof ( cacheKey ) , " ai:%s:%s " , achievement - > badge_name , iconState = = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED ? " unlocked " : " locked " ) ;
if ( RC_OK = = rc_client_achievement_get_image_url ( achievement , iconState , temp , sizeof ( temp ) ) ) {
Achievements : : DownloadImageIfMissing ( cacheKey , std : : move ( std : : string ( temp ) ) ) ;
if ( g_iconCache . BindIconTexture ( & dc , cacheKey ) ) {
2023-07-03 12:39:49 +00:00
dc . Draw ( ) - > DrawTexRect ( Bounds ( bounds . x + padding , bounds . y + padding , iconSpace , iconSpace ) , 0.0f , 0.0f , 1.0f , 1.0f , whiteAlpha ( alpha ) ) ;
2023-06-27 21:31:15 +00:00
}
2023-07-03 12:39:49 +00:00
dc . Flush ( ) ;
dc . RebindTexture ( ) ;
2023-06-18 13:13:16 +00:00
}
2023-07-12 17:21:08 +00:00
dc . Flush ( ) ;
dc . PopScissor ( ) ;
2023-06-17 20:22:59 +00:00
}
2023-06-27 12:58:39 +00:00
void RenderGameAchievementSummary ( UIContext & dc , const Bounds & bounds , float alpha ) {
2023-06-19 21:47:23 +00:00
using namespace UI ;
UI : : Drawable background = dc . theme - > itemStyle . background ;
2023-06-27 12:58:39 +00:00
background . color = alphaMul ( background . color , alpha ) ;
2023-06-22 07:27:58 +00:00
uint32_t fgColor = colorAlpha ( dc . theme - > itemStyle . fgColor , alpha ) ;
2023-06-19 21:47:23 +00:00
float iconSpace = 64.0f ;
2023-06-20 07:18:47 +00:00
dc . Flush ( ) ;
2023-06-19 21:47:23 +00:00
dc . Begin ( ) ;
dc . FillRect ( background , bounds ) ;
2023-06-21 12:32:10 +00:00
dc . SetFontStyle ( dc . theme - > uiFont ) ;
2023-06-27 21:31:15 +00:00
const rc_client_game_t * gameInfo = rc_client_get_game_info ( Achievements : : GetClient ( ) ) ;
2023-06-19 21:47:23 +00:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-06-27 21:31:15 +00:00
dc . DrawTextRect ( gameInfo - > title , bounds . Inset ( iconSpace + 5.0f , 2.0f , 5.0f , 5.0f ) , fgColor , ALIGN_TOPLEFT ) ;
2023-06-19 21:47:23 +00:00
std : : string description = Achievements : : GetGameAchievementSummary ( ) ;
dc . SetFontScale ( 0.66f , 0.66f ) ;
2023-06-22 07:27:58 +00:00
dc . DrawTextRect ( description . c_str ( ) , bounds . Inset ( iconSpace + 5.0f , 38.0f , 5.0f , 5.0f ) , fgColor , ALIGN_TOPLEFT ) ;
2023-06-19 21:47:23 +00:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
dc . Flush ( ) ;
2023-07-03 22:49:17 +00:00
char url [ 512 ] ;
char cacheKey [ 256 ] ;
snprintf ( cacheKey , sizeof ( cacheKey ) , " gi:%s " , gameInfo - > badge_name ) ;
if ( RC_OK = = rc_client_game_get_image_url ( gameInfo , url , sizeof ( url ) ) ) {
Achievements : : DownloadImageIfMissing ( cacheKey , std : : move ( std : : string ( url ) ) ) ;
if ( g_iconCache . BindIconTexture ( & dc , cacheKey ) ) {
2023-06-27 21:31:15 +00:00
dc . Draw ( ) - > DrawTexRect ( Bounds ( bounds . x , bounds . y , iconSpace , iconSpace ) , 0.0f , 0.0f , 1.0f , 1.0f , whiteAlpha ( alpha ) ) ;
}
2023-06-19 21:47:23 +00:00
}
dc . Flush ( ) ;
dc . RebindTexture ( ) ;
}
2023-07-16 20:07:26 +00:00
void RenderLeaderboardSummary ( UIContext & dc , const rc_client_leaderboard_t * leaderboard , AchievementRenderStyle style , const Bounds & bounds , float alpha , float startTime , float time_s , bool hasFocus ) {
2023-06-27 09:12:25 +00:00
using namespace UI ;
2023-07-16 20:07:26 +00:00
UI : : Drawable background = dc . theme - > itemStyle . background ;
if ( hasFocus ) {
background = dc . theme - > itemFocusedStyle . background ;
}
background . color = alphaMul ( background . color , alpha ) ;
uint32_t fgColor = alphaMul ( dc . theme - > itemStyle . fgColor , alpha ) ;
2023-06-27 09:12:25 +00:00
if ( style = = AchievementRenderStyle : : UNLOCKED ) {
float mixWhite = pow ( Clamp ( ( float ) ( 1.0f - ( time_s - startTime ) ) , 0.0f , 1.0f ) , 3.0f ) ;
background . color = colorBlend ( 0xFFE0FFFF , background . color , mixWhite ) ;
}
dc . Flush ( ) ;
dc . Begin ( ) ;
dc . FillRect ( background , bounds ) ;
dc . SetFontStyle ( dc . theme - > uiFont ) ;
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-07-10 17:26:41 +00:00
dc . DrawTextRect ( DeNull ( leaderboard - > title ) , bounds . Inset ( 12.0f , 2.0f , 5.0f , 5.0f ) , fgColor , ALIGN_TOPLEFT ) ;
2023-06-27 09:12:25 +00:00
dc . SetFontScale ( 0.66f , 0.66f ) ;
2023-07-17 10:02:56 +00:00
dc . DrawTextRectSqueeze ( DeNull ( leaderboard - > description ) , bounds . Inset ( 12.0f , 39.0f , 5.0f , 5.0f ) , fgColor , ALIGN_TOPLEFT ) ;
2023-06-27 09:12:25 +00:00
/*
char temp [ 64 ] ;
snprintf ( temp , sizeof ( temp ) , " %d " , leaderboard . points ) ;
dc . SetFontScale ( 1.5f , 1.5f ) ;
dc . DrawTextRect ( temp , bounds . Expand ( - 5.0f , - 5.0f ) , fgColor , ALIGN_RIGHT | ALIGN_VCENTER ) ;
dc . Flush ( ) ;
*/
2023-07-17 10:02:56 +00:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-06-27 09:12:25 +00:00
dc . Flush ( ) ;
dc . RebindTexture ( ) ;
}
2023-06-19 21:47:23 +00:00
2023-07-16 20:07:26 +00:00
void RenderLeaderboardEntry ( UIContext & dc , const rc_client_leaderboard_entry_t * entry , const Bounds & bounds , float alpha , bool hasFocus ) {
2023-07-02 15:12:46 +00:00
using namespace UI ;
UI : : Drawable background = dc . theme - > itemStyle . background ;
2023-07-16 20:07:26 +00:00
if ( hasFocus ) {
background = dc . theme - > itemFocusedStyle . background ;
}
2023-07-02 15:12:46 +00:00
background . color = alphaMul ( background . color , alpha ) ;
uint32_t fgColor = alphaMul ( dc . theme - > itemStyle . fgColor , alpha ) ;
float iconSize = 64.0f ;
float numberSpace = 128.0f ;
float iconLeft = numberSpace + 5.0f ;
float iconSpace = numberSpace + 5.0f + iconSize ;
2023-07-10 17:26:41 +00:00
// Sanity check
if ( ! entry - > user ) {
return ;
}
2023-07-02 15:12:46 +00:00
dc . Flush ( ) ;
dc . Begin ( ) ;
dc . FillRect ( background , bounds ) ;
dc . SetFontStyle ( dc . theme - > uiFont ) ;
dc . SetFontScale ( 1.5f , 1.5f ) ;
dc . DrawTextRect ( StringFromFormat ( " %d " , entry - > rank ) . c_str ( ) , Bounds ( bounds . x + 4.0f , bounds . y + 4.0f , numberSpace - 10.0f , bounds . h - 4.0f * 2.0f ) , fgColor , ALIGN_TOPRIGHT ) ;
dc . SetFontScale ( 1.0f , 1.0f ) ;
dc . DrawTextRect ( entry - > user , bounds . Inset ( iconSpace + 5.0f , 2.0f , 5.0f , 5.0f ) , fgColor , ALIGN_TOPLEFT ) ;
dc . SetFontScale ( 0.66f , 0.66f ) ;
2023-07-10 17:26:41 +00:00
dc . DrawTextRect ( DeNull ( entry - > display ) , bounds . Inset ( iconSpace + 5.0f , 38.0f , 5.0f , 5.0f ) , fgColor , ALIGN_TOPLEFT ) ;
2023-07-02 15:12:46 +00:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
dc . Flush ( ) ;
// Come up with a unique name for the icon entry.
2023-07-03 22:49:17 +00:00
char cacheKey [ 256 ] ;
snprintf ( cacheKey , sizeof ( cacheKey ) , " lbe:%s " , entry - > user ) ;
2023-07-02 15:12:46 +00:00
char temp [ 512 ] ;
if ( RC_OK = = rc_client_leaderboard_entry_get_user_image_url ( entry , temp , sizeof ( temp ) ) ) {
2023-07-03 22:49:17 +00:00
Achievements : : DownloadImageIfMissing ( cacheKey , std : : move ( std : : string ( temp ) ) ) ;
if ( g_iconCache . BindIconTexture ( & dc , cacheKey ) ) {
2023-07-02 15:12:46 +00:00
dc . Draw ( ) - > DrawTexRect ( Bounds ( bounds . x + iconLeft , bounds . y + 4.0f , 64.0f , 64.0f ) , 0.0f , 0.0f , 1.0f , 1.0f , whiteAlpha ( alpha ) ) ;
}
}
dc . Flush ( ) ;
dc . RebindTexture ( ) ;
}
2023-06-17 20:22:59 +00:00
void AchievementView : : Draw ( UIContext & dc ) {
2023-07-16 20:07:26 +00:00
RenderAchievement ( dc , achievement_ , AchievementRenderStyle : : LISTED , bounds_ , 1.0f , 0.0f , 0.0f , HasFocus ( ) ) ;
2023-06-17 20:22:59 +00:00
}
void AchievementView : : GetContentDimensions ( const UIContext & dc , float & w , float & h ) const {
2023-06-27 21:31:15 +00:00
MeasureAchievement ( dc , achievement_ , AchievementRenderStyle : : LISTED , & w , & h ) ;
2023-06-21 10:17:44 +00:00
}
void AchievementView : : Click ( ) {
// In debug builds, clicking achievements will show them being unlocked (which may be a lie).
# ifdef _DEBUG
2023-07-03 12:39:49 +00:00
static int type = 0 ;
type + + ;
type = type % 4 ;
switch ( type ) {
case 0 : g_OSD . ShowAchievementUnlocked ( achievement_ - > id ) ; break ;
case 1 : g_OSD . ShowAchievementProgress ( achievement_ - > id , 2.0f ) ; break ;
case 2 : g_OSD . ShowChallengeIndicator ( achievement_ - > id , true ) ; break ;
case 3 : g_OSD . ShowChallengeIndicator ( achievement_ - > id , false ) ; break ;
}
2023-06-21 10:17:44 +00:00
# endif
2023-06-17 20:22:59 +00:00
}
2023-06-19 21:47:23 +00:00
2023-07-02 15:12:46 +00:00
void GameAchievementSummaryView : : Draw ( UIContext & dc ) {
2023-06-27 12:58:39 +00:00
RenderGameAchievementSummary ( dc , bounds_ , 1.0f ) ;
2023-07-02 15:12:46 +00:00
}
void GameAchievementSummaryView : : GetContentDimensions ( const UIContext & dc , float & w , float & h ) const {
2023-07-15 17:00:28 +00:00
// Somehow wrong!
2023-06-27 12:58:39 +00:00
MeasureGameAchievementSummary ( dc , & w , & h ) ;
2023-07-02 15:12:46 +00:00
}
2023-06-27 09:12:25 +00:00
void LeaderboardSummaryView : : Draw ( UIContext & dc ) {
2023-07-16 20:07:26 +00:00
RenderLeaderboardSummary ( dc , leaderboard_ , AchievementRenderStyle : : LISTED , bounds_ , 1.0f , 0.0f , 0.0f , HasFocus ( ) ) ;
2023-06-27 09:12:25 +00:00
}
void LeaderboardSummaryView : : GetContentDimensions ( const UIContext & dc , float & w , float & h ) const {
MeasureLeaderboardSummary ( dc , leaderboard_ , & w , & h ) ;
}
2023-07-02 15:12:46 +00:00
void LeaderboardEntryView : : Draw ( UIContext & dc ) {
2023-07-16 20:07:26 +00:00
RenderLeaderboardEntry ( dc , entry_ , bounds_ , 1.0f , HasFocus ( ) ) ;
2023-06-19 21:47:23 +00:00
}
2023-07-02 15:12:46 +00:00
void LeaderboardEntryView : : GetContentDimensions ( const UIContext & dc , float & w , float & h ) const {
MeasureLeaderboardEntry ( dc , entry_ , & w , & h ) ;
2023-06-19 21:47:23 +00:00
}