2022-08-28 15:32:00 +00:00
# include <algorithm>
2021-02-22 00:38:02 +00:00
# include <sstream>
2023-06-20 12:40:46 +00:00
2013-06-05 21:03:23 +00:00
# include "UI/OnScreenDisplay.h"
2020-10-01 11:05:04 +00:00
# include "Common/Data/Color/RGBAUtil.h"
2022-09-04 04:16:59 +00:00
# include "Common/Data/Encoding/Utf8.h"
2020-10-04 21:24:14 +00:00
# include "Common/Render/TextureAtlas.h"
# include "Common/Render/DrawBuffer.h"
2023-06-20 16:39:30 +00:00
# include "Common/Math/math_util.h"
2023-06-21 10:17:44 +00:00
# include "Common/UI/IconCache.h"
# include "UI/RetroAchievementScreens.h"
2023-08-03 14:19:18 +00:00
# include "UI/DebugOverlay.h"
2023-09-05 14:43:45 +00:00
# include "UI/Root.h"
2013-05-22 16:00:06 +00:00
2020-10-04 18:48:47 +00:00
# include "Common/UI/Context.h"
2023-06-30 15:15:49 +00:00
# include "Common/System/OSD.h"
2014-12-31 15:50:23 +00:00
2020-08-15 18:53:08 +00:00
# include "Common/TimeUtil.h"
2023-06-19 13:50:36 +00:00
# include "Common/Net/HTTPClient.h"
# include "Core/Config.h"
2020-08-15 18:53:08 +00:00
2023-07-10 17:26:41 +00:00
static inline const char * DeNull ( const char * ptr ) {
return ptr ? ptr : " " ;
}
2024-01-21 09:07:15 +00:00
extern bool g_TakeScreenshot ;
2023-07-07 13:23:19 +00:00
static const float g_atlasIconSize = 36.0f ;
static const float extraTextScale = 0.7f ;
static uint32_t GetNoticeBackgroundColor ( NoticeLevel type ) {
2023-06-20 13:07:01 +00:00
// Colors from Infima
switch ( type ) {
2023-07-07 13:23:19 +00:00
case NoticeLevel : : ERROR : return 0x3530d5 ; // danger-darker
case NoticeLevel : : WARN : return 0x009ed9 ; // warning-darker
case NoticeLevel : : INFO : return 0x706760 ; // gray-700
case NoticeLevel : : SUCCESS : return 0x008b00 ; // nice green
2023-06-20 13:07:01 +00:00
default : return 0x606770 ;
}
}
2023-07-07 13:23:19 +00:00
static ImageID GetOSDIcon ( NoticeLevel level ) {
switch ( level ) {
2023-07-13 09:24:37 +00:00
case NoticeLevel : : INFO : return ImageID ( " I_INFO " ) ;
2023-07-07 13:23:19 +00:00
case NoticeLevel : : ERROR : return ImageID ( " I_CROSS " ) ;
case NoticeLevel : : WARN : return ImageID ( " I_WARNING " ) ;
2023-07-13 09:24:37 +00:00
case NoticeLevel : : SUCCESS : return ImageID ( " I_CHECKMARK " ) ;
2023-06-20 15:27:34 +00:00
default : return ImageID : : invalid ( ) ;
}
}
2023-07-07 13:23:19 +00:00
static NoticeLevel GetNoticeLevel ( OSDType type ) {
switch ( type ) {
2024-01-31 21:08:26 +00:00
case OSDType : : MESSAGE_INFO :
return NoticeLevel : : INFO ;
2023-07-07 13:23:19 +00:00
case OSDType : : MESSAGE_ERROR :
2024-01-31 21:08:26 +00:00
case OSDType : : MESSAGE_ERROR_DUMP :
case OSDType : : MESSAGE_CENTERED_ERROR :
return NoticeLevel : : ERROR ;
2024-01-20 22:01:08 +00:00
case OSDType : : MESSAGE_WARNING :
case OSDType : : MESSAGE_CENTERED_WARNING :
return NoticeLevel : : WARN ;
2024-01-31 21:08:26 +00:00
case OSDType : : MESSAGE_SUCCESS :
return NoticeLevel : : SUCCESS ;
default :
return NoticeLevel : : SUCCESS ;
2023-06-21 10:17:44 +00:00
}
2023-07-07 13:23:19 +00:00
}
2023-06-21 10:17:44 +00:00
2023-06-27 21:31:15 +00:00
// Align only matters here for the ASCII-only flag.
2023-07-07 13:23:19 +00:00
static void MeasureNotice ( const UIContext & dc , NoticeLevel level , const std : : string & text , const std : : string & details , const std : : string & iconName , int align , float * width , float * height , float * height1 ) {
2024-10-10 09:55:07 +00:00
dc . MeasureText ( dc . theme - > uiFont , 1.0f , 1.0f , text , width , height , align ) ;
2023-06-27 21:31:15 +00:00
2023-06-20 23:07:57 +00:00
* height1 = * height ;
float width2 = 0.0f , height2 = 0.0f ;
2023-07-07 13:23:19 +00:00
if ( ! details . empty ( ) ) {
2024-10-10 09:55:07 +00:00
dc . MeasureText ( dc . theme - > uiFont , extraTextScale , extraTextScale , details , & width2 , & height2 , align ) ;
2023-06-20 23:07:57 +00:00
* width = std : : max ( * width , width2 ) ;
* height + = 5.0f + height2 ;
}
2023-06-20 15:27:34 +00:00
2023-08-25 14:32:39 +00:00
float iconW = 0.0f ;
float iconH = 0.0f ;
if ( ! iconName . empty ( ) & & ! startsWith ( iconName , " I_ " ) ) { // Check for atlas image. Bit hacky, but we choose prefixes for icon IDs anyway in a way that this is safe.
2023-06-21 12:32:10 +00:00
// Normal entry but with a cached icon.
int iconWidth , iconHeight ;
2023-07-07 13:23:19 +00:00
if ( g_iconCache . GetDimensions ( iconName , & iconWidth , & iconHeight ) ) {
2023-06-21 12:32:10 +00:00
* width + = 5.0f + iconWidth ;
2023-08-25 14:32:39 +00:00
iconW = iconWidth ;
iconH = iconHeight ;
}
} else {
ImageID iconID = iconName . empty ( ) ? GetOSDIcon ( level ) : ImageID ( iconName . c_str ( ) ) ;
if ( iconID . isValid ( ) ) {
dc . Draw ( ) - > GetAtlas ( ) - > measureImage ( iconID , & iconW , & iconH ) ;
2023-06-21 12:32:10 +00:00
}
2023-06-20 15:27:34 +00:00
}
2023-08-25 14:32:39 +00:00
iconW + = 5.0f ;
* width + = iconW + 12.0f ;
* height = std : : max ( * height , iconH + 5.0f ) ;
2023-06-20 15:27:34 +00:00
}
2023-07-07 13:23:19 +00:00
// Align only matters here for the ASCII-only flag.
static void MeasureOSDEntry ( const UIContext & dc , const OnScreenDisplay : : Entry & entry , int align , float * width , float * height , float * height1 ) {
2023-06-21 10:17:44 +00:00
if ( entry . type = = OSDType : : ACHIEVEMENT_UNLOCKED ) {
2023-06-27 21:31:15 +00:00
const rc_client_achievement_t * achievement = rc_client_get_achievement_info ( Achievements : : GetClient ( ) , entry . numericID ) ;
MeasureAchievement ( dc , achievement , AchievementRenderStyle : : UNLOCKED , width , height ) ;
2023-07-07 13:23:19 +00:00
* width = 550.0f ;
* height1 = * height ;
} else {
MeasureNotice ( dc , GetNoticeLevel ( entry . type ) , entry . text , entry . text2 , entry . iconName , align , width , height , height1 ) ;
2023-06-21 10:17:44 +00:00
}
2023-07-07 13:23:19 +00:00
}
2023-06-21 10:17:44 +00:00
2023-07-07 13:23:19 +00:00
static void RenderNotice ( UIContext & dc , Bounds bounds , float height1 , NoticeLevel level , const std : : string & text , const std : : string & details , const std : : string & iconName , int align , float alpha ) {
UI : : Drawable background = UI : : Drawable ( colorAlpha ( GetNoticeBackgroundColor ( level ) , alpha ) ) ;
2023-06-20 15:27:34 +00:00
uint32_t foreGround = whiteAlpha ( alpha ) ;
2023-07-03 20:54:25 +00:00
dc . DrawRectDropShadow ( bounds , 12.0f , 0.7f * alpha ) ;
2023-06-20 15:27:34 +00:00
dc . FillRect ( background , bounds ) ;
2023-08-25 14:32:39 +00:00
float iconW = 0.0f ;
float iconH = 0.0f ;
if ( ! iconName . empty ( ) & & ! startsWith ( iconName , " I_ " ) ) {
2023-06-21 12:32:10 +00:00
dc . Flush ( ) ;
// Normal entry but with a cached icon.
2023-07-07 13:23:19 +00:00
Draw : : Texture * texture = g_iconCache . BindIconTexture ( & dc , iconName ) ;
2023-06-21 12:32:10 +00:00
if ( texture ) {
2023-08-25 14:32:39 +00:00
iconW = texture - > Width ( ) ;
iconH = texture - > Height ( ) ;
dc . Draw ( ) - > DrawTexRect ( Bounds ( bounds . x + 2.5f , bounds . y + 2.5f , iconW , iconH ) , 0.0f , 0.0f , 1.0f , 1.0f , foreGround ) ;
2023-06-21 12:32:10 +00:00
dc . Flush ( ) ;
dc . RebindTexture ( ) ;
}
dc . Begin ( ) ;
2023-08-25 14:32:39 +00:00
} else {
ImageID iconID = iconName . empty ( ) ? GetOSDIcon ( level ) : ImageID ( iconName . c_str ( ) ) ;
if ( iconID . isValid ( ) ) {
// Atlas icon.
dc . Draw ( ) - > GetAtlas ( ) - > measureImage ( iconID , & iconW , & iconH ) ;
if ( ! iconName . empty ( ) ) {
2023-12-20 09:35:02 +00:00
Bounds iconBounds = Bounds ( bounds . x + 2.5f , bounds . y + 2.5f , iconW , iconH ) ;
2023-08-25 14:32:39 +00:00
// If it's not a preset OSD icon, give it some background to blend in. The RA icon for example
// easily melts into the orange of warnings otherwise.
dc . FillRect ( UI : : Drawable ( 0x50000000 ) , iconBounds . Expand ( 2.0f ) ) ;
}
dc . DrawImageVGradient ( iconID , foreGround , foreGround , Bounds ( bounds . x + 2.5f , bounds . y + 2.5f , iconW , iconH ) ) ;
}
2023-06-20 15:27:34 +00:00
}
2023-06-21 12:32:10 +00:00
// Make room
2023-08-25 14:32:39 +00:00
bounds . x + = iconW + 5.0f ;
bounds . w - = iconW + 5.0f ;
2023-06-21 12:32:10 +00:00
2024-10-10 09:55:07 +00:00
dc . DrawTextShadowRect ( text , bounds . Inset ( 0.0f , 1.0f , 0.0f , 0.0f ) , foreGround , ( align & FLAG_DYNAMIC_ASCII ) ) ;
2023-06-20 23:07:57 +00:00
2023-07-07 13:23:19 +00:00
if ( ! details . empty ( ) ) {
2023-06-20 23:07:57 +00:00
Bounds bottomTextBounds = bounds . Inset ( 3.0f , height1 + 5.0f , 3.0f , 3.0f ) ;
2023-07-07 13:23:19 +00:00
UI : : Drawable backgroundDark = UI : : Drawable ( colorAlpha ( darkenColor ( GetNoticeBackgroundColor ( level ) ) , alpha ) ) ;
2023-06-20 23:07:57 +00:00
dc . FillRect ( backgroundDark , bottomTextBounds ) ;
dc . SetFontScale ( extraTextScale , extraTextScale ) ;
2024-05-24 12:25:10 +00:00
dc . DrawTextRect ( details , bottomTextBounds , foreGround , ( align & FLAG_DYNAMIC_ASCII ) | ALIGN_LEFT ) ;
2023-06-20 23:07:57 +00:00
}
2023-06-21 12:32:10 +00:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-06-20 15:27:34 +00:00
}
2023-07-07 13:23:19 +00:00
static void RenderOSDEntry ( UIContext & dc , const OnScreenDisplay : : Entry & entry , Bounds bounds , float height1 , int align , float alpha ) {
if ( entry . type = = OSDType : : ACHIEVEMENT_UNLOCKED ) {
2023-06-27 21:31:15 +00:00
const rc_client_achievement_t * achievement = rc_client_get_achievement_info ( Achievements : : GetClient ( ) , entry . numericID ) ;
if ( achievement ) {
2023-07-16 20:07:26 +00:00
RenderAchievement ( dc , achievement , AchievementRenderStyle : : UNLOCKED , bounds , alpha , entry . startTime , time_now_d ( ) , false ) ;
2023-06-27 21:31:15 +00:00
}
2023-07-07 13:23:19 +00:00
return ;
2023-06-27 21:31:15 +00:00
} else {
RenderNotice ( dc , bounds , height1 , GetNoticeLevel ( entry . type ) , entry . text , entry . text2 , entry . iconName , align , alpha ) ;
2023-07-07 13:23:19 +00:00
}
}
2023-07-31 21:13:52 +00:00
static void MeasureOSDProgressBar ( const UIContext & dc , const OnScreenDisplay : : Entry & bar , float * width , float * height ) {
2023-06-20 19:26:42 +00:00
* height = 36 ;
* width = 450.0f ;
}
2023-07-31 21:13:52 +00:00
static void RenderOSDProgressBar ( UIContext & dc , const OnScreenDisplay : : Entry & entry , Bounds bounds , int align , float alpha ) {
2023-07-03 12:39:49 +00:00
dc . DrawRectDropShadow ( bounds , 12.0f , 0.7f * alpha ) ;
2023-06-20 19:26:42 +00:00
2023-06-20 22:15:23 +00:00
uint32_t backgroundColor = colorAlpha ( 0x806050 , alpha ) ;
uint32_t progressBackgroundColor = colorAlpha ( 0xa08070 , alpha ) ;
if ( entry . maxValue > entry . minValue ) {
// Normal progress bar
UI : : Drawable background = UI : : Drawable ( backgroundColor ) ;
UI : : Drawable progressBackground = UI : : Drawable ( progressBackgroundColor ) ;
2023-06-20 19:26:42 +00:00
2023-06-20 22:15:23 +00:00
float ratio = ( float ) ( entry . progress - entry . minValue ) / ( float ) entry . maxValue ;
2023-06-20 19:26:42 +00:00
2023-06-20 22:15:23 +00:00
Bounds boundLeft = bounds ;
Bounds boundRight = bounds ;
boundLeft . w * = ratio ;
boundRight . x + = ratio * boundRight . w ;
boundRight . w * = ( 1.0f - ratio ) ;
dc . FillRect ( progressBackground , boundLeft ) ;
dc . FillRect ( background , boundRight ) ;
} else {
// Indeterminate spinner
float alpha = cos ( time_now_d ( ) * 5.0 ) * 0.5f + 0.5f ;
uint32_t pulse = colorBlend ( backgroundColor , progressBackgroundColor , alpha ) ;
UI : : Drawable background = UI : : Drawable ( pulse ) ;
dc . FillRect ( background , bounds ) ;
}
2023-06-20 19:26:42 +00:00
dc . SetFontStyle ( dc . theme - > uiFont ) ;
2023-06-21 12:32:10 +00:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-06-20 19:26:42 +00:00
2024-10-10 09:55:07 +00:00
dc . DrawTextShadowRect ( entry . text , bounds , colorAlpha ( 0xFFFFFFFF , alpha ) , ( align & FLAG_DYNAMIC_ASCII ) | ALIGN_CENTER ) ;
2023-06-20 19:26:42 +00:00
}
2023-07-03 12:39:49 +00:00
static void MeasureLeaderboardTracker ( UIContext & dc , const std : : string & text , float * width , float * height ) {
2024-10-10 09:55:07 +00:00
dc . MeasureText ( dc . GetFontStyle ( ) , 1.0f , 1.0f , text , width , height ) ;
2023-07-24 12:39:40 +00:00
* width + = 16.0f ;
2023-07-03 12:39:49 +00:00
* height + = 10.0f ;
}
static void RenderLeaderboardTracker ( UIContext & dc , const Bounds & bounds , const std : : string & text , float alpha ) {
// TODO: Awful color.
uint32_t backgroundColor = colorAlpha ( 0x806050 , alpha ) ;
UI : : Drawable background = UI : : Drawable ( backgroundColor ) ;
dc . DrawRectDropShadow ( bounds , 12.0f , 0.7f * alpha ) ;
dc . FillRect ( background , bounds ) ;
dc . SetFontStyle ( dc . theme - > uiFont ) ;
dc . SetFontScale ( 1.0f , 1.0f ) ;
2024-10-10 09:55:07 +00:00
dc . DrawTextShadowRect ( text , bounds . Inset ( 5.0f , 5.0f ) , colorAlpha ( 0xFFFFFFFF , alpha ) , ALIGN_VCENTER | ALIGN_HCENTER ) ;
2023-07-03 12:39:49 +00:00
}
2014-12-31 15:50:23 +00:00
void OnScreenMessagesView : : Draw ( UIContext & dc ) {
2024-01-21 09:07:15 +00:00
if ( ! g_Config . bShowOnScreenMessages | | g_TakeScreenshot ) {
2023-06-19 13:50:36 +00:00
return ;
}
2023-06-21 12:32:10 +00:00
dc . Flush ( ) ;
2023-06-20 19:26:42 +00:00
double now = time_now_d ( ) ;
2023-07-31 21:13:52 +00:00
const float padding = 5.0f ;
2023-06-27 21:31:15 +00:00
2023-07-31 21:13:52 +00:00
const float fadeinCoef = 1.0f / OnScreenDisplay : : FadeinTime ( ) ;
2023-07-03 12:39:49 +00:00
const float fadeoutCoef = 1.0f / OnScreenDisplay : : FadeoutTime ( ) ;
2023-07-16 06:55:48 +00:00
float sidebarAlpha = g_OSD . SidebarAlpha ( ) ;
2023-07-31 21:13:52 +00:00
struct LayoutEdge {
float height ;
float maxWidth ;
float alpha ;
} ;
struct MeasuredEntry {
float w ;
float h ;
float h1 ;
float alpha ;
int align ;
int align2 ;
AchievementRenderStyle style ;
} ;
// Grab all the entries. Makes a copy so we can release the lock ASAP.
const std : : vector < OnScreenDisplay : : Entry > entries = g_OSD . Entries ( ) ;
2023-07-10 08:39:44 +00:00
2023-07-31 21:13:52 +00:00
std : : vector < MeasuredEntry > measuredEntries ;
measuredEntries . resize ( entries . size ( ) ) ;
2023-07-10 08:39:44 +00:00
2023-07-31 21:13:52 +00:00
// Indexed by the enum ScreenEdgePosition.
LayoutEdge edges [ ( size_t ) ScreenEdgePosition : : VALUE_COUNT ] { } ;
for ( size_t i = 0 ; i < ( size_t ) ScreenEdgePosition : : VALUE_COUNT ; i + + ) {
edges [ i ] . alpha = sidebarAlpha ;
}
edges [ ( size_t ) ScreenEdgePosition : : TOP_CENTER ] . alpha = 1.0f ;
2023-07-03 12:39:49 +00:00
2023-07-31 21:13:52 +00:00
ScreenEdgePosition typeEdges [ ( size_t ) OSDType : : VALUE_COUNT ] { } ;
// Default to top.
for ( int i = 0 ; i < ( size_t ) OSDType : : VALUE_COUNT ; i + + ) {
typeEdges [ i ] = ScreenEdgePosition : : TOP_CENTER ;
2023-06-27 21:31:15 +00:00
}
2023-08-01 10:52:09 +00:00
typeEdges [ ( size_t ) OSDType : : ACHIEVEMENT_CHALLENGE_INDICATOR ] = ( ScreenEdgePosition ) g_Config . iAchievementsChallengePos ;
typeEdges [ ( size_t ) OSDType : : ACHIEVEMENT_PROGRESS ] = ( ScreenEdgePosition ) g_Config . iAchievementsProgressPos ;
2023-07-31 22:13:33 +00:00
typeEdges [ ( size_t ) OSDType : : LEADERBOARD_TRACKER ] = ( ScreenEdgePosition ) g_Config . iAchievementsLeaderboardTrackerPos ;
2023-08-01 10:52:09 +00:00
typeEdges [ ( size_t ) OSDType : : LEADERBOARD_STARTED_FAILED ] = ( ScreenEdgePosition ) g_Config . iAchievementsLeaderboardStartedOrFailedPos ;
typeEdges [ ( size_t ) OSDType : : LEADERBOARD_SUBMITTED ] = ( ScreenEdgePosition ) g_Config . iAchievementsLeaderboardSubmittedPos ;
typeEdges [ ( size_t ) OSDType : : ACHIEVEMENT_UNLOCKED ] = ( ScreenEdgePosition ) g_Config . iAchievementsUnlockedPos ;
2024-01-20 22:01:08 +00:00
typeEdges [ ( size_t ) OSDType : : MESSAGE_CENTERED_WARNING ] = ScreenEdgePosition : : CENTER ;
2024-01-31 21:08:26 +00:00
typeEdges [ ( size_t ) OSDType : : MESSAGE_CENTERED_ERROR ] = ScreenEdgePosition : : CENTER ;
2023-06-27 21:31:15 +00:00
2023-07-31 21:13:52 +00:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-06-20 19:26:42 +00:00
2023-07-31 21:13:52 +00:00
// First pass: Measure all the sides.
for ( size_t i = 0 ; i < entries . size ( ) ; i + + ) {
const auto & entry = entries [ i ] ;
auto & measuredEntry = measuredEntries [ i ] ;
2023-06-20 19:26:42 +00:00
2023-07-31 21:13:52 +00:00
ScreenEdgePosition pos = typeEdges [ ( size_t ) entry . type ] ;
2023-08-01 10:52:09 +00:00
if ( pos = = ScreenEdgePosition : : VALUE_COUNT | | pos = = ( ScreenEdgePosition ) - 1 ) {
2023-07-31 22:25:32 +00:00
// NONE.
continue ;
}
2022-08-24 08:20:33 +00:00
2023-07-31 21:13:52 +00:00
measuredEntry . align = 0 ;
measuredEntry . align2 = 0 ;
2022-09-04 04:16:59 +00:00
// If we have newlines, we may be looking at ASCII debug output. But let's verify.
2023-07-31 21:13:52 +00:00
if ( entry . text . find ( ' \n ' ) ! = std : : string : : npos ) {
2024-10-10 09:55:07 +00:00
if ( ! UTF8StringHasNonASCII ( entry . text ) )
2023-07-31 21:13:52 +00:00
measuredEntry . align | = FLAG_DYNAMIC_ASCII ;
}
if ( entry . text2 . find ( ' \n ' ) ! = std : : string : : npos ) {
2024-10-10 09:55:07 +00:00
if ( ! UTF8StringHasNonASCII ( entry . text2 ) )
2023-07-31 21:13:52 +00:00
measuredEntry . align2 | = FLAG_DYNAMIC_ASCII ;
2022-08-24 08:20:33 +00:00
}
2023-06-27 21:31:15 +00:00
switch ( entry . type ) {
2023-07-31 21:13:52 +00:00
case OSDType : : ACHIEVEMENT_PROGRESS :
{
const rc_client_achievement_t * achievement = rc_client_get_achievement_info ( Achievements : : GetClient ( ) , entry . numericID ) ;
if ( ! achievement )
continue ;
measuredEntry . style = AchievementRenderStyle : : PROGRESS_INDICATOR ;
MeasureAchievement ( dc , achievement , measuredEntry . style , & measuredEntry . w , & measuredEntry . h ) ;
break ;
}
case OSDType : : ACHIEVEMENT_CHALLENGE_INDICATOR :
{
const rc_client_achievement_t * achievement = rc_client_get_achievement_info ( Achievements : : GetClient ( ) , entry . numericID ) ;
if ( ! achievement )
continue ;
measuredEntry . style = AchievementRenderStyle : : CHALLENGE_INDICATOR ;
MeasureAchievement ( dc , achievement , measuredEntry . style , & measuredEntry . w , & measuredEntry . h ) ;
break ;
}
case OSDType : : LEADERBOARD_TRACKER :
{
MeasureLeaderboardTracker ( dc , entry . text , & measuredEntry . w , & measuredEntry . h ) ;
break ;
}
2023-06-27 21:31:15 +00:00
case OSDType : : ACHIEVEMENT_UNLOCKED :
{
const rc_client_achievement_t * achievement = rc_client_get_achievement_info ( Achievements : : GetClient ( ) , entry . numericID ) ;
2023-07-31 21:13:52 +00:00
if ( ! achievement )
continue ;
measuredEntry . style = AchievementRenderStyle : : UNLOCKED ;
MeasureAchievement ( dc , achievement , AchievementRenderStyle : : UNLOCKED , & measuredEntry . w , & measuredEntry . h ) ;
measuredEntry . h1 = measuredEntry . h ;
measuredEntry . w = 550.0f ;
2023-06-27 21:31:15 +00:00
break ;
}
2023-07-31 21:13:52 +00:00
case OSDType : : PROGRESS_BAR :
MeasureOSDProgressBar ( dc , entry , & measuredEntry . w , & measuredEntry . h ) ;
break ;
2023-06-27 21:31:15 +00:00
default :
2023-07-31 21:13:52 +00:00
MeasureOSDEntry ( dc , entry , measuredEntry . align , & measuredEntry . w , & measuredEntry . h , & measuredEntry . h1 ) ;
2023-06-27 21:31:15 +00:00
break ;
}
2023-06-20 15:27:34 +00:00
2023-07-31 21:13:52 +00:00
float enterAlpha = saturatef ( ( float ) ( now - entry . startTime ) * fadeoutCoef ) ;
float leaveAlpha = saturatef ( ( float ) ( entry . endTime - now ) * fadeoutCoef ) ;
float alpha = std : : min ( enterAlpha , leaveAlpha ) ;
measuredEntry . alpha = alpha ;
edges [ ( size_t ) pos ] . height + = ( measuredEntry . h + 4.0f ) * alpha ;
edges [ ( size_t ) pos ] . maxWidth = std : : max ( edges [ ( size_t ) pos ] . maxWidth , measuredEntry . w ) ;
}
2024-04-09 12:10:02 +00:00
std : : vector < ClickZone > clickZones ;
2023-09-04 08:54:17 +00:00
2023-07-31 21:13:52 +00:00
// Now, perform layout for all 8 edges.
for ( size_t i = 0 ; i < ( size_t ) ScreenEdgePosition : : VALUE_COUNT ; i + + ) {
if ( edges [ i ] . height = = 0.0f ) {
// Nothing on this side, ignore it entirely.
continue ;
}
2023-06-20 15:27:34 +00:00
2023-07-31 21:13:52 +00:00
// First, compute the start position.
float y = padding ;
int horizAdj = 0 ;
int vertAdj = 0 ;
switch ( ( ScreenEdgePosition ) i ) {
case ScreenEdgePosition : : TOP_LEFT : horizAdj = - 1 ; vertAdj = - 1 ; break ;
case ScreenEdgePosition : : CENTER_LEFT : horizAdj = - 1 ; break ;
case ScreenEdgePosition : : BOTTOM_LEFT : horizAdj = - 1 ; vertAdj = 1 ; break ;
case ScreenEdgePosition : : TOP_RIGHT : horizAdj = 1 ; vertAdj = - 1 ; break ;
case ScreenEdgePosition : : CENTER_RIGHT : horizAdj = 1 ; break ;
case ScreenEdgePosition : : BOTTOM_RIGHT : horizAdj = 1 ; vertAdj = 1 ; break ;
case ScreenEdgePosition : : TOP_CENTER : vertAdj = - 1 ; break ;
case ScreenEdgePosition : : BOTTOM_CENTER : vertAdj = 1 ; break ;
2024-01-20 22:01:08 +00:00
case ScreenEdgePosition : : CENTER : break ;
2023-07-31 21:13:52 +00:00
default : break ;
2022-08-24 08:20:33 +00:00
}
2023-06-20 15:27:34 +00:00
2023-07-31 21:13:52 +00:00
if ( vertAdj = = 0 ) {
// Center vertically
y = ( bounds_ . h - edges [ i ] . height ) * 0.5f ;
} else if ( vertAdj = = 1 ) {
y = ( bounds_ . h - edges [ i ] . height ) ;
2013-10-13 10:05:50 +00:00
}
2023-06-20 16:39:30 +00:00
2023-07-31 21:13:52 +00:00
// Then, loop through the entries and those belonging here, get rendered here.
for ( size_t j = 0 ; j < ( size_t ) entries . size ( ) ; j + + ) {
auto & entry = entries [ j ] ;
if ( typeEdges [ ( size_t ) entry . type ] ! = ( ScreenEdgePosition ) i ) { // yes, i
continue ;
}
auto & measuredEntry = measuredEntries [ j ] ;
float alpha = measuredEntry . alpha * edges [ i ] . alpha ;
Bounds b ( padding , y , measuredEntry . w , measuredEntry . h ) ;
if ( horizAdj = = 0 ) {
// Centered
b . x = ( bounds_ . w - b . w ) * 0.5f ;
} else if ( horizAdj = = 1 ) {
// Right-aligned
b . x = bounds_ . w - ( b . w + padding ) ;
}
switch ( entry . type ) {
case OSDType : : ACHIEVEMENT_PROGRESS :
case OSDType : : ACHIEVEMENT_CHALLENGE_INDICATOR :
{
const rc_client_achievement_t * achievement = rc_client_get_achievement_info ( Achievements : : GetClient ( ) , entry . numericID ) ;
RenderAchievement ( dc , achievement , measuredEntry . style , b , alpha , entry . startTime , now , false ) ;
break ;
}
case OSDType : : LEADERBOARD_TRACKER :
RenderLeaderboardTracker ( dc , b , entry . text , alpha ) ;
break ;
case OSDType : : PROGRESS_BAR :
RenderOSDProgressBar ( dc , entry , b , 0 , alpha ) ;
break ;
default :
{
// Scale down if height doesn't fit.
float scale = 1.0f ;
if ( measuredEntry . h > bounds_ . h - y ) {
// Scale down!
scale = std : : max ( 0.15f , ( bounds_ . h - y ) / measuredEntry . h ) ;
dc . SetFontScale ( scale , scale ) ;
b . w * = scale ;
b . h * = scale ;
}
float alpha = Clamp ( ( float ) ( entry . endTime - now ) * 4.0f , 0.0f , 1.0f ) ;
RenderOSDEntry ( dc , entry , b , measuredEntry . h1 , measuredEntry . align , alpha ) ;
2023-09-04 09:04:15 +00:00
switch ( entry . type ) {
case OSDType : : MESSAGE_INFO :
case OSDType : : MESSAGE_SUCCESS :
case OSDType : : MESSAGE_WARNING :
case OSDType : : MESSAGE_ERROR :
2024-01-31 21:08:26 +00:00
case OSDType : : MESSAGE_CENTERED_ERROR :
case OSDType : : MESSAGE_CENTERED_WARNING :
2023-09-04 09:04:15 +00:00
case OSDType : : MESSAGE_ERROR_DUMP :
case OSDType : : MESSAGE_FILE_LINK :
case OSDType : : ACHIEVEMENT_UNLOCKED :
// Save the location of the popup, for easy dismissal.
2024-04-09 12:10:02 +00:00
clickZones . push_back ( ClickZone { ( int ) j , b } ) ;
2023-09-04 09:04:15 +00:00
break ;
2023-09-21 14:41:09 +00:00
default :
break ;
2023-09-04 09:04:15 +00:00
}
2023-07-31 21:13:52 +00:00
break ;
}
}
2023-09-04 08:01:07 +00:00
2023-07-31 21:13:52 +00:00
y + = ( measuredEntry . h + 4.0f ) * measuredEntry . alpha ;
}
2013-05-22 16:00:06 +00:00
}
2023-09-04 08:01:07 +00:00
2023-09-04 09:04:15 +00:00
std : : lock_guard < std : : mutex > lock ( clickMutex_ ) ;
2024-04-09 12:10:02 +00:00
clickZones_ = clickZones ;
2014-12-31 15:50:23 +00:00
}
2021-02-22 00:38:02 +00:00
std : : string OnScreenMessagesView : : DescribeText ( ) const {
std : : stringstream ss ;
2023-06-20 13:07:01 +00:00
const auto & entries = g_OSD . Entries ( ) ;
for ( auto iter = entries . begin ( ) ; iter ! = entries . end ( ) ; + + iter ) {
if ( iter ! = entries . begin ( ) ) {
2021-02-22 00:38:02 +00:00
ss < < " \n " ;
}
ss < < iter - > text ;
}
return ss . str ( ) ;
}
2023-09-04 09:04:15 +00:00
// Asynchronous!
bool OnScreenMessagesView : : Dismiss ( float x , float y ) {
bool dismissed = false ;
std : : lock_guard < std : : mutex > lock ( clickMutex_ ) ;
double now = time_now_d ( ) ;
for ( auto & zone : clickZones_ ) {
if ( zone . bounds . Contains ( x , y ) ) {
2024-04-09 12:10:02 +00:00
g_OSD . ClickEntry ( zone . index , now ) ;
2023-09-04 09:04:15 +00:00
dismissed = true ;
2023-09-04 08:54:17 +00:00
}
2023-09-04 09:04:15 +00:00
}
return dismissed ;
}
bool OSDOverlayScreen : : UnsyncTouch ( const TouchInput & touch ) {
// Don't really need to forward.
// UIScreen::UnsyncTouch(touch);
if ( ( touch . flags & TOUCH_DOWN ) & & osmView_ ) {
return osmView_ - > Dismiss ( touch . x , touch . y ) ;
2023-09-04 08:54:17 +00:00
} else {
return false ;
2023-09-04 08:01:07 +00:00
}
}
2023-06-19 13:50:36 +00:00
void OSDOverlayScreen : : CreateViews ( ) {
root_ = new UI : : AnchorLayout ( ) ;
2023-07-06 14:34:54 +00:00
root_ - > SetTag ( " OSDOverlayScreen " ) ;
2023-09-04 09:04:15 +00:00
osmView_ = root_ - > Add ( new OnScreenMessagesView ( new UI : : AnchorLayoutParams ( 0.0f , 0.0f , 0.0f , 0.0f ) ) ) ;
2023-06-19 13:50:36 +00:00
}
2023-07-07 13:23:19 +00:00
2023-12-11 11:41:44 +00:00
void OSDOverlayScreen : : DrawForeground ( UIContext & ui ) {
2023-08-03 14:19:18 +00:00
DebugOverlay debugOverlay = ( DebugOverlay ) g_Config . iDebugOverlay ;
// Special case control for now, since it uses the control mapper that's owned by EmuScreen.
if ( debugOverlay ! = DebugOverlay : : OFF & & debugOverlay ! = DebugOverlay : : CONTROL ) {
UIContext * uiContext = screenManager ( ) - > getUIContext ( ) ;
DrawDebugOverlay ( uiContext , uiContext - > GetLayoutBounds ( ) , debugOverlay ) ;
}
}
2023-09-05 14:43:45 +00:00
void OSDOverlayScreen : : update ( ) {
// Partial version of UIScreen::update() but doesn't do event processing to avoid duplicate event processing.
bool vertical = UseVerticalLayout ( ) ;
if ( vertical ! = lastVertical_ ) {
RecreateViews ( ) ;
lastVertical_ = vertical ;
}
DoRecreateViews ( ) ;
}
2023-07-07 13:23:19 +00:00
void NoticeView : : GetContentDimensionsBySpec ( const UIContext & dc , UI : : MeasureSpec horiz , UI : : MeasureSpec vert , float & w , float & h ) const {
Bounds bounds ( 0 , 0 , layoutParams_ - > width , layoutParams_ - > height ) ;
if ( bounds . w < 0 ) {
// If there's no size, let's grow as big as we want.
bounds . w = horiz . size ;
}
if ( bounds . h < 0 ) {
bounds . h = vert . size ;
}
ApplyBoundsBySpec ( bounds , horiz , vert ) ;
MeasureNotice ( dc , level_ , text_ , detailsText_ , iconName_ , 0 , & w , & h , & height1_ ) ;
2024-01-31 21:08:26 +00:00
// Layout hack! Some weird problems with the layout that I can't figure out right now..
2024-01-31 22:17:51 +00:00
if ( squishy_ ) {
w = 50.0 ;
}
2023-07-07 13:23:19 +00:00
}
void NoticeView : : Draw ( UIContext & dc ) {
2024-01-31 21:08:26 +00:00
dc . PushScissor ( bounds_ ) ;
2023-07-07 13:23:19 +00:00
RenderNotice ( dc , bounds_ , height1_ , level_ , text_ , detailsText_ , iconName_ , 0 , 1.0f ) ;
2024-01-31 21:08:26 +00:00
dc . PopScissor ( ) ;
2023-07-07 13:23:19 +00:00
}