2022-08-28 08:32:00 -07:00
# include <algorithm>
2021-02-21 16:38:02 -08:00
# include <sstream>
2023-06-20 14:40:46 +02:00
2013-06-05 23:03:23 +02:00
# include "UI/OnScreenDisplay.h"
2020-10-01 13:05:04 +02:00
# include "Common/Data/Color/RGBAUtil.h"
2022-09-03 21:16:59 -07:00
# include "Common/Data/Encoding/Utf8.h"
2020-10-04 23:24:14 +02:00
# include "Common/Render/TextureAtlas.h"
# include "Common/Render/DrawBuffer.h"
2023-06-20 18:39:30 +02:00
# include "Common/Math/math_util.h"
2023-06-21 12:17:44 +02:00
# include "Common/UI/IconCache.h"
# include "UI/RetroAchievementScreens.h"
2023-08-03 16:19:18 +02:00
# include "UI/DebugOverlay.h"
2023-09-05 16:43:45 +02:00
# include "UI/Root.h"
2013-05-22 18:00:06 +02:00
2020-10-04 20:48:47 +02:00
# include "Common/UI/Context.h"
2023-06-30 17:15:49 +02:00
# include "Common/System/OSD.h"
2014-12-31 16:50:23 +01:00
2020-08-15 20:53:08 +02:00
# include "Common/TimeUtil.h"
2023-06-19 15:50:36 +02:00
# include "Common/Net/HTTPClient.h"
# include "Core/Config.h"
2020-08-15 20:53:08 +02:00
2023-07-10 19:26:41 +02:00
static inline const char * DeNull ( const char * ptr ) {
return ptr ? ptr : " " ;
}
2024-01-21 10:07:15 +01:00
extern bool g_TakeScreenshot ;
2023-07-07 15:23:19 +02:00
static const float g_atlasIconSize = 36.0f ;
static const float extraTextScale = 0.7f ;
static uint32_t GetNoticeBackgroundColor ( NoticeLevel type ) {
2023-06-20 15:07:01 +02:00
// Colors from Infima
switch ( type ) {
2023-07-07 15:23:19 +02: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 15:07:01 +02:00
default : return 0x606770 ;
}
}
2023-07-07 15:23:19 +02:00
static ImageID GetOSDIcon ( NoticeLevel level ) {
switch ( level ) {
2023-07-13 11:24:37 +02:00
case NoticeLevel : : INFO : return ImageID ( " I_INFO " ) ;
2023-07-07 15:23:19 +02:00
case NoticeLevel : : ERROR : return ImageID ( " I_CROSS " ) ;
case NoticeLevel : : WARN : return ImageID ( " I_WARNING " ) ;
2023-07-13 11:24:37 +02:00
case NoticeLevel : : SUCCESS : return ImageID ( " I_CHECKMARK " ) ;
2023-06-20 17:27:34 +02:00
default : return ImageID : : invalid ( ) ;
}
}
2023-07-07 15:23:19 +02:00
static NoticeLevel GetNoticeLevel ( OSDType type ) {
switch ( type ) {
2024-01-31 22:08:26 +01:00
case OSDType : : MESSAGE_INFO :
return NoticeLevel : : INFO ;
2023-07-07 15:23:19 +02:00
case OSDType : : MESSAGE_ERROR :
2024-01-31 22:08:26 +01:00
case OSDType : : MESSAGE_ERROR_DUMP :
case OSDType : : MESSAGE_CENTERED_ERROR :
return NoticeLevel : : ERROR ;
2024-01-20 23:01:08 +01:00
case OSDType : : MESSAGE_WARNING :
case OSDType : : MESSAGE_CENTERED_WARNING :
return NoticeLevel : : WARN ;
2024-01-31 22:08:26 +01:00
case OSDType : : MESSAGE_SUCCESS :
return NoticeLevel : : SUCCESS ;
default :
return NoticeLevel : : SUCCESS ;
2023-06-21 12:17:44 +02:00
}
2023-07-07 15:23:19 +02:00
}
2023-06-21 12:17:44 +02:00
2023-06-27 23:31:15 +02:00
// Align only matters here for the ASCII-only flag.
2023-07-07 15:23:19 +02: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 ) {
dc . MeasureText ( dc . theme - > uiFont , 1.0f , 1.0f , text . c_str ( ) , width , height , align ) ;
2023-06-27 23:31:15 +02:00
2023-06-21 01:07:57 +02:00
* height1 = * height ;
float width2 = 0.0f , height2 = 0.0f ;
2023-07-07 15:23:19 +02:00
if ( ! details . empty ( ) ) {
dc . MeasureText ( dc . theme - > uiFont , extraTextScale , extraTextScale , details . c_str ( ) , & width2 , & height2 , align ) ;
2023-06-21 01:07:57 +02:00
* width = std : : max ( * width , width2 ) ;
* height + = 5.0f + height2 ;
}
2023-06-20 17:27:34 +02:00
2023-08-25 16:32:39 +02: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 14:32:10 +02:00
// Normal entry but with a cached icon.
int iconWidth , iconHeight ;
2023-07-07 15:23:19 +02:00
if ( g_iconCache . GetDimensions ( iconName , & iconWidth , & iconHeight ) ) {
2023-06-21 14:32:10 +02:00
* width + = 5.0f + iconWidth ;
2023-08-25 16:32:39 +02: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 14:32:10 +02:00
}
2023-06-20 17:27:34 +02:00
}
2023-08-25 16:32:39 +02:00
iconW + = 5.0f ;
* width + = iconW + 12.0f ;
* height = std : : max ( * height , iconH + 5.0f ) ;
2023-06-20 17:27:34 +02:00
}
2023-07-07 15:23:19 +02: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 12:17:44 +02:00
if ( entry . type = = OSDType : : ACHIEVEMENT_UNLOCKED ) {
2023-06-27 23:31:15 +02: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 15:23:19 +02: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 12:17:44 +02:00
}
2023-07-07 15:23:19 +02:00
}
2023-06-21 12:17:44 +02:00
2023-07-07 15:23:19 +02: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 17:27:34 +02:00
uint32_t foreGround = whiteAlpha ( alpha ) ;
2023-07-03 22:54:25 +02:00
dc . DrawRectDropShadow ( bounds , 12.0f , 0.7f * alpha ) ;
2023-06-20 17:27:34 +02:00
dc . FillRect ( background , bounds ) ;
2023-08-25 16:32:39 +02:00
float iconW = 0.0f ;
float iconH = 0.0f ;
if ( ! iconName . empty ( ) & & ! startsWith ( iconName , " I_ " ) ) {
2023-06-21 14:32:10 +02:00
dc . Flush ( ) ;
// Normal entry but with a cached icon.
2023-07-07 15:23:19 +02:00
Draw : : Texture * texture = g_iconCache . BindIconTexture ( & dc , iconName ) ;
2023-06-21 14:32:10 +02:00
if ( texture ) {
2023-08-25 16:32:39 +02: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 14:32:10 +02:00
dc . Flush ( ) ;
dc . RebindTexture ( ) ;
}
dc . Begin ( ) ;
2023-08-25 16:32:39 +02: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 12:35:02 +03:00
Bounds iconBounds = Bounds ( bounds . x + 2.5f , bounds . y + 2.5f , iconW , iconH ) ;
2023-08-25 16:32:39 +02: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 17:27:34 +02:00
}
2023-06-21 14:32:10 +02:00
// Make room
2023-08-25 16:32:39 +02:00
bounds . x + = iconW + 5.0f ;
bounds . w - = iconW + 5.0f ;
2023-06-21 14:32:10 +02:00
2023-07-07 15:23:19 +02:00
dc . DrawTextShadowRect ( text . c_str ( ) , bounds . Inset ( 0.0f , 1.0f , 0.0f , 0.0f ) , foreGround , ( align & FLAG_DYNAMIC_ASCII ) ) ;
2023-06-21 01:07:57 +02:00
2023-07-07 15:23:19 +02:00
if ( ! details . empty ( ) ) {
2023-06-21 01:07:57 +02:00
Bounds bottomTextBounds = bounds . Inset ( 3.0f , height1 + 5.0f , 3.0f , 3.0f ) ;
2023-07-07 15:23:19 +02:00
UI : : Drawable backgroundDark = UI : : Drawable ( colorAlpha ( darkenColor ( GetNoticeBackgroundColor ( level ) ) , alpha ) ) ;
2023-06-21 01:07:57 +02:00
dc . FillRect ( backgroundDark , bottomTextBounds ) ;
dc . SetFontScale ( extraTextScale , extraTextScale ) ;
2023-07-07 15:23:19 +02:00
dc . DrawTextRect ( details . c_str ( ) , bottomTextBounds , foreGround , ( align & FLAG_DYNAMIC_ASCII ) | ALIGN_LEFT ) ;
2023-06-21 01:07:57 +02:00
}
2023-06-21 14:32:10 +02:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-06-20 17:27:34 +02:00
}
2023-07-07 15:23:19 +02: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 23:31:15 +02:00
const rc_client_achievement_t * achievement = rc_client_get_achievement_info ( Achievements : : GetClient ( ) , entry . numericID ) ;
if ( achievement ) {
2023-07-16 22:07:26 +02:00
RenderAchievement ( dc , achievement , AchievementRenderStyle : : UNLOCKED , bounds , alpha , entry . startTime , time_now_d ( ) , false ) ;
2023-06-27 23:31:15 +02:00
}
2023-07-07 15:23:19 +02:00
return ;
2023-06-27 23:31:15 +02:00
} else {
RenderNotice ( dc , bounds , height1 , GetNoticeLevel ( entry . type ) , entry . text , entry . text2 , entry . iconName , align , alpha ) ;
2023-07-07 15:23:19 +02:00
}
}
2023-07-31 23:13:52 +02:00
static void MeasureOSDProgressBar ( const UIContext & dc , const OnScreenDisplay : : Entry & bar , float * width , float * height ) {
2023-06-20 21:26:42 +02:00
* height = 36 ;
* width = 450.0f ;
}
2023-07-31 23:13:52 +02:00
static void RenderOSDProgressBar ( UIContext & dc , const OnScreenDisplay : : Entry & entry , Bounds bounds , int align , float alpha ) {
2023-06-20 21:26:42 +02:00
uint32_t foreGround = whiteAlpha ( alpha ) ;
2023-07-03 14:39:49 +02:00
dc . DrawRectDropShadow ( bounds , 12.0f , 0.7f * alpha ) ;
2023-06-20 21:26:42 +02:00
2023-06-21 00:15:23 +02: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 21:26:42 +02:00
2023-06-21 00:15:23 +02:00
float ratio = ( float ) ( entry . progress - entry . minValue ) / ( float ) entry . maxValue ;
2023-06-20 21:26:42 +02:00
2023-06-21 00:15:23 +02: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 21:26:42 +02:00
dc . SetFontStyle ( dc . theme - > uiFont ) ;
2023-06-21 14:32:10 +02:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-06-20 21:26:42 +02:00
2023-07-31 23:13:52 +02:00
dc . DrawTextShadowRect ( entry . text . c_str ( ) , bounds , colorAlpha ( 0xFFFFFFFF , alpha ) , ( align & FLAG_DYNAMIC_ASCII ) | ALIGN_CENTER ) ;
2023-06-20 21:26:42 +02:00
}
2023-07-03 14:39:49 +02:00
static void MeasureLeaderboardTracker ( UIContext & dc , const std : : string & text , float * width , float * height ) {
dc . MeasureText ( dc . GetFontStyle ( ) , 1.0f , 1.0f , text . c_str ( ) , width , height ) ;
2023-07-24 14:39:40 +02:00
* width + = 16.0f ;
2023-07-03 14:39:49 +02: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 ) ;
2023-07-24 14:39:40 +02:00
dc . DrawTextShadowRect ( text . c_str ( ) , bounds . Inset ( 5.0f , 5.0f ) , colorAlpha ( 0xFFFFFFFF , alpha ) , ALIGN_VCENTER | ALIGN_HCENTER ) ;
2023-07-03 14:39:49 +02:00
}
2014-12-31 16:50:23 +01:00
void OnScreenMessagesView : : Draw ( UIContext & dc ) {
2024-01-21 10:07:15 +01:00
if ( ! g_Config . bShowOnScreenMessages | | g_TakeScreenshot ) {
2023-06-19 15:50:36 +02:00
return ;
}
2023-06-21 14:32:10 +02:00
dc . Flush ( ) ;
2023-06-20 21:26:42 +02:00
double now = time_now_d ( ) ;
2023-07-31 23:13:52 +02:00
const float padding = 5.0f ;
2023-06-27 23:31:15 +02:00
2023-07-31 23:13:52 +02:00
const float fadeinCoef = 1.0f / OnScreenDisplay : : FadeinTime ( ) ;
2023-07-03 14:39:49 +02:00
const float fadeoutCoef = 1.0f / OnScreenDisplay : : FadeoutTime ( ) ;
2023-07-16 08:55:48 +02:00
float sidebarAlpha = g_OSD . SidebarAlpha ( ) ;
2023-07-31 23:13:52 +02: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 10:39:44 +02:00
2023-07-31 23:13:52 +02:00
std : : vector < MeasuredEntry > measuredEntries ;
measuredEntries . resize ( entries . size ( ) ) ;
2023-07-10 10:39:44 +02:00
2023-07-31 23:13:52 +02: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 14:39:49 +02:00
2023-07-31 23:13:52 +02: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 23:31:15 +02:00
}
2023-08-01 12:52:09 +02:00
typeEdges [ ( size_t ) OSDType : : ACHIEVEMENT_CHALLENGE_INDICATOR ] = ( ScreenEdgePosition ) g_Config . iAchievementsChallengePos ;
typeEdges [ ( size_t ) OSDType : : ACHIEVEMENT_PROGRESS ] = ( ScreenEdgePosition ) g_Config . iAchievementsProgressPos ;
2023-08-01 00:13:33 +02:00
typeEdges [ ( size_t ) OSDType : : LEADERBOARD_TRACKER ] = ( ScreenEdgePosition ) g_Config . iAchievementsLeaderboardTrackerPos ;
2023-08-01 12:52:09 +02: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 23:01:08 +01:00
typeEdges [ ( size_t ) OSDType : : MESSAGE_CENTERED_WARNING ] = ScreenEdgePosition : : CENTER ;
2024-01-31 22:08:26 +01:00
typeEdges [ ( size_t ) OSDType : : MESSAGE_CENTERED_ERROR ] = ScreenEdgePosition : : CENTER ;
2023-06-27 23:31:15 +02:00
2023-07-31 23:13:52 +02:00
dc . SetFontScale ( 1.0f , 1.0f ) ;
2023-06-20 21:26:42 +02:00
2023-07-31 23:13:52 +02: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 21:26:42 +02:00
2023-07-31 23:13:52 +02:00
ScreenEdgePosition pos = typeEdges [ ( size_t ) entry . type ] ;
2023-08-01 12:52:09 +02:00
if ( pos = = ScreenEdgePosition : : VALUE_COUNT | | pos = = ( ScreenEdgePosition ) - 1 ) {
2023-08-01 00:25:32 +02:00
// NONE.
continue ;
}
2022-08-24 10:20:33 +02:00
2023-07-31 23:13:52 +02:00
measuredEntry . align = 0 ;
measuredEntry . align2 = 0 ;
2022-09-03 21:16:59 -07:00
// If we have newlines, we may be looking at ASCII debug output. But let's verify.
2023-07-31 23:13:52 +02:00
if ( entry . text . find ( ' \n ' ) ! = std : : string : : npos ) {
2023-06-27 23:31:15 +02:00
if ( ! UTF8StringHasNonASCII ( entry . text . c_str ( ) ) )
2023-07-31 23:13:52 +02:00
measuredEntry . align | = FLAG_DYNAMIC_ASCII ;
}
if ( entry . text2 . find ( ' \n ' ) ! = std : : string : : npos ) {
if ( ! UTF8StringHasNonASCII ( entry . text2 . c_str ( ) ) )
measuredEntry . align2 | = FLAG_DYNAMIC_ASCII ;
2022-08-24 10:20:33 +02:00
}
2023-06-27 23:31:15 +02:00
switch ( entry . type ) {
2023-07-31 23:13:52 +02: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 23:31:15 +02:00
case OSDType : : ACHIEVEMENT_UNLOCKED :
{
const rc_client_achievement_t * achievement = rc_client_get_achievement_info ( Achievements : : GetClient ( ) , entry . numericID ) ;
2023-07-31 23:13:52 +02: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 23:31:15 +02:00
break ;
}
2023-07-31 23:13:52 +02:00
case OSDType : : PROGRESS_BAR :
MeasureOSDProgressBar ( dc , entry , & measuredEntry . w , & measuredEntry . h ) ;
break ;
2023-06-27 23:31:15 +02:00
default :
2023-07-31 23:13:52 +02:00
MeasureOSDEntry ( dc , entry , measuredEntry . align , & measuredEntry . w , & measuredEntry . h , & measuredEntry . h1 ) ;
2023-06-27 23:31:15 +02:00
break ;
}
2023-06-20 17:27:34 +02:00
2023-07-31 23:13:52 +02: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 ) ;
}
2023-09-04 11:04:15 +02:00
std : : vector < ClickZone > dismissZones ;
2023-09-04 10:54:17 +02:00
2023-07-31 23:13:52 +02: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 17:27:34 +02:00
2023-07-31 23:13:52 +02: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 23:01:08 +01:00
case ScreenEdgePosition : : CENTER : break ;
2023-07-31 23:13:52 +02:00
default : break ;
2022-08-24 10:20:33 +02:00
}
2023-06-20 17:27:34 +02:00
2023-07-31 23:13:52 +02: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 12:05:50 +02:00
}
2023-06-20 18:39:30 +02:00
2023-07-31 23:13:52 +02: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 11:04:15 +02:00
switch ( entry . type ) {
case OSDType : : MESSAGE_INFO :
case OSDType : : MESSAGE_SUCCESS :
case OSDType : : MESSAGE_WARNING :
case OSDType : : MESSAGE_ERROR :
2024-01-31 22:08:26 +01:00
case OSDType : : MESSAGE_CENTERED_ERROR :
case OSDType : : MESSAGE_CENTERED_WARNING :
2023-09-04 11:04:15 +02:00
case OSDType : : MESSAGE_ERROR_DUMP :
case OSDType : : MESSAGE_FILE_LINK :
case OSDType : : ACHIEVEMENT_UNLOCKED :
// Save the location of the popup, for easy dismissal.
dismissZones . push_back ( ClickZone { ( int ) j , b } ) ;
break ;
2023-09-21 16:41:09 +02:00
default :
break ;
2023-09-04 11:04:15 +02:00
}
2023-07-31 23:13:52 +02:00
break ;
}
}
2023-09-04 10:01:07 +02:00
2023-07-31 23:13:52 +02:00
y + = ( measuredEntry . h + 4.0f ) * measuredEntry . alpha ;
}
2013-05-22 18:00:06 +02:00
}
2023-09-04 10:01:07 +02:00
2023-09-04 11:04:15 +02:00
std : : lock_guard < std : : mutex > lock ( clickMutex_ ) ;
clickZones_ = dismissZones ;
2014-12-31 16:50:23 +01:00
}
2021-02-21 16:38:02 -08:00
std : : string OnScreenMessagesView : : DescribeText ( ) const {
std : : stringstream ss ;
2023-06-20 15:07:01 +02:00
const auto & entries = g_OSD . Entries ( ) ;
for ( auto iter = entries . begin ( ) ; iter ! = entries . end ( ) ; + + iter ) {
if ( iter ! = entries . begin ( ) ) {
2021-02-21 16:38:02 -08:00
ss < < " \n " ;
}
ss < < iter - > text ;
}
return ss . str ( ) ;
}
2023-09-04 11:04:15 +02: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 ) ) {
g_OSD . DismissEntry ( zone . index , now ) ;
dismissed = true ;
2023-09-04 10:54:17 +02:00
}
2023-09-04 11:04:15 +02: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 10:54:17 +02:00
} else {
return false ;
2023-09-04 10:01:07 +02:00
}
}
2023-06-19 15:50:36 +02:00
void OSDOverlayScreen : : CreateViews ( ) {
root_ = new UI : : AnchorLayout ( ) ;
2023-07-06 16:34:54 +02:00
root_ - > SetTag ( " OSDOverlayScreen " ) ;
2023-09-04 11:04:15 +02:00
osmView_ = root_ - > Add ( new OnScreenMessagesView ( new UI : : AnchorLayoutParams ( 0.0f , 0.0f , 0.0f , 0.0f ) ) ) ;
2023-06-19 15:50:36 +02:00
}
2023-07-07 15:23:19 +02:00
2023-12-11 12:41:44 +01:00
void OSDOverlayScreen : : DrawForeground ( UIContext & ui ) {
2023-08-03 16:19:18 +02: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 16:43:45 +02: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 15:23:19 +02: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 22:08:26 +01:00
// Layout hack! Some weird problems with the layout that I can't figure out right now..
2024-01-31 23:17:51 +01:00
if ( squishy_ ) {
w = 50.0 ;
}
2023-07-07 15:23:19 +02:00
}
void NoticeView : : Draw ( UIContext & dc ) {
2024-01-31 22:08:26 +01:00
dc . PushScissor ( bounds_ ) ;
2023-07-07 15:23:19 +02:00
RenderNotice ( dc , bounds_ , height1_ , level_ , text_ , detailsText_ , iconName_ , 0 , 1.0f ) ;
2024-01-31 22:08:26 +01:00
dc . PopScissor ( ) ;
2023-07-07 15:23:19 +02:00
}