// Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "ScreenCreditsWidget.h" #include "Components/BackgroundBlur.h" #include "Components/RichTextBlock.h" #include "Engine/Font.h" #include "Engine/Texture2D.h" #include "Framework/Application/SlateApplication.h" #include "HttpModule.h" #include "ImageUtils.h" #include "Interfaces/IHttpResponse.h" #include "Math/UnrealMathUtility.h" #include "Misc/Base64.h" #include "Rendering/DrawElements.h" #include "Runtime/Launch/Resources/Version.h" #include "Slate/SlateGameResources.h" #include "UObject/ConstructorHelpers.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SRichTextHyperlink.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SCompoundWidget.h" #include #include class SCreditImage : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SCreditImage) {} SLATE_END_ARGS() public: void Construct(const FArguments& InArgs, const FSlateBrush* Brush) { if (ensure(Brush)) { ChildSlot[SNew(SBox).VAlign(VAlign_Center)[SNew(SImage).Image(Brush)]]; } } }; class SCreditHyperlinkImage : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SCreditImage) {} SLATE_END_ARGS() public: void Construct( const FArguments& InArgs, const FSlateBrush* Brush, const FString& Url) { if (ensure(Brush)) { ButtonStyle.SetNormal(*Brush); ButtonStyle.SetHovered(*Brush); ButtonStyle.SetPressed(*Brush); ChildSlot [SNew(SButton).ButtonStyle(&ButtonStyle).OnClicked_Lambda([Url]() { FPlatformProcess::LaunchURL(*Url, NULL, NULL); return FReply::Handled(); })]; this->SetCursor(EMouseCursor::Hand); } } private: FButtonStyle ButtonStyle; }; class SCreditHyperlink : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SCreditImage) {} SLATE_END_ARGS() public: void Construct( const FArguments& InArgs, const FString& Text, const FString& Url, const UCreditsDecorator* InDecorator) { TSharedPtr model = MakeShareable(new FSlateHyperlinkRun::FWidgetViewModel); ChildSlot[SNew(SRichTextHyperlink, model.ToSharedRef()) .Text(FText::FromString(Text)) .OnNavigate_Lambda([Url, InDecorator]() { if (Url.Equals("popup")) { InDecorator->PopupClicked.Execute(); } else { FPlatformProcess::LaunchURL(*Url, NULL, NULL); } })]; } }; class FScreenCreditsDecorator : public FRichTextDecorator { public: FScreenCreditsDecorator( URichTextBlock* InOwner, UCreditsDecorator* InDecorator) : FRichTextDecorator(InOwner), Decorator(InDecorator) {} virtual bool Supports( const FTextRunParseResults& RunParseResult, const FString& Text) const override { if (RunParseResult.Name == TEXT("credits") && (RunParseResult.MetaData.Contains(TEXT("id")) || RunParseResult.MetaData.Contains(TEXT("url")))) { return true; } return false; } protected: virtual TSharedPtr CreateDecoratorWidget( const FTextRunInfo& RunInfo, const FTextBlockStyle&) const override { FString Text; FString Url; const FSlateBrush* Brush = nullptr; if (RunInfo.MetaData.Contains(TEXT("url"))) { Url = *RunInfo.MetaData[TEXT("url")]; } if (RunInfo.MetaData.Contains(TEXT("text"))) { Text = *RunInfo.MetaData[TEXT("text")]; } if (RunInfo.MetaData.Contains(TEXT("id"))) { int32 id = FCString::Atoi(*RunInfo.MetaData[TEXT("id")]); Brush = Decorator->FindImageBrush(id); } if (Brush) { if (Url.IsEmpty()) { return SNew(SCreditImage, Brush); } else { return SNew(SCreditHyperlinkImage, Brush, Url); } } else return SNew(SCreditHyperlink, Text, Url, Decorator); } private: UCreditsDecorator* Decorator; }; UCreditsDecorator::UCreditsDecorator( const FObjectInitializer& ObjectInitializer) : URichTextBlockDecorator(ObjectInitializer) {} TSharedPtr UCreditsDecorator::CreateDecorator(URichTextBlock* InOwner) { return MakeShareable(new FScreenCreditsDecorator(InOwner, this)); } const FSlateBrush* UCreditsDecorator::FindImageBrush(int32 id) { if (CreditsWidget->_creditImages.Num() > id) { return CreditsWidget->_creditImages[id]; } return nullptr; } UScreenCreditsWidget::UScreenCreditsWidget( const FObjectInitializer& ObjectInitializer) : UUserWidget(ObjectInitializer) { static ConstructorHelpers::FObjectFinder RobotoFontObj( *UWidget::GetDefaultFontName()); _font = FSlateFontInfo(RobotoFontObj.Object, 8); } UScreenCreditsWidget::~UScreenCreditsWidget() { for (int i = 0; i < _creditImages.Num(); i++) { if (_creditImages[i]) { delete _creditImages[i]; } } } void UScreenCreditsWidget::OnPopupClicked() { _showPopup = !_showPopup; BackgroundBlur->SetVisibility( _showPopup ? ESlateVisibility::Visible : ESlateVisibility::Hidden); } void UScreenCreditsWidget::NativeConstruct() { Super::NativeConstruct(); if (RichTextOnScreen) { RichTextOnScreen->SetDefaultFont(_font); RichTextOnScreen->SetDefaultColorAndOpacity( FSlateColor(FLinearColor(1.f, 1.f, 1.f, 1.f))); _decoratorOnScreen = static_cast(RichTextOnScreen->GetDecoratorByClass( UCreditsDecorator::StaticClass())); _decoratorOnScreen->PopupClicked.BindUObject( this, &UScreenCreditsWidget::OnPopupClicked); _decoratorOnScreen->CreditsWidget = this; } if (RichTextPopup) { RichTextPopup->SetDefaultFont(_font); RichTextPopup->SetDefaultColorAndOpacity( FSlateColor(FLinearColor(1.f, 1.f, 1.f, 1.f))); _decoratorPopup = static_cast( RichTextPopup->GetDecoratorByClass(UCreditsDecorator::StaticClass())); _decoratorPopup->CreditsWidget = this; } } void UScreenCreditsWidget::HandleImageRequest( FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, int32 id) { UTexture2D* texture = nullptr; if (bSucceeded && HttpResponse.IsValid() && HttpResponse->GetContentLength() > 0 && (texture = FImageUtils::ImportBufferAsTexture2D( HttpResponse->GetContent())) != nullptr) { texture->SRGB = true; texture->UpdateResource(); _textures.Add(texture); FTexturePlatformData* pPlatformData = texture->GetPlatformData(); int32 SizeX = pPlatformData->SizeX; int32 SizeY = pPlatformData->SizeY; _creditImages[id] = new FSlateImageBrush(texture, FVector2D(SizeX, SizeY)); } // Only update credits after all of the images are done loading. --_numImagesLoading; if (_numImagesLoading == 0) { SetCredits(_credits, _onScreenCredits); } } std::string UScreenCreditsWidget::LoadImage(const std::string& url) { const std::string base64Prefix = "data:image/png;base64,"; if (url.rfind(base64Prefix, 0) == 0) { TArray dataBuffer; FString base64 = UTF8_TO_TCHAR(url.c_str() + base64Prefix.length()); if (FBase64::Decode(base64, dataBuffer)) { UTexture2D* texture = FImageUtils::ImportBufferAsTexture2D(dataBuffer); texture->SRGB = true; texture->UpdateResource(); _textures.Add(texture); FTexturePlatformData* pPlatformData = texture->GetPlatformData(); int32 SizeX = pPlatformData->SizeX; int32 SizeY = pPlatformData->SizeY; _creditImages.Add(new FSlateImageBrush(texture, FVector2D(SizeX, SizeY))); } } else { ++_numImagesLoading; TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); _creditImages.AddDefaulted(); HttpRequest->OnProcessRequestComplete().BindUObject( this, &UScreenCreditsWidget::HandleImageRequest, _creditImages.Num() - 1); HttpRequest->SetURL(UTF8_TO_TCHAR(url.c_str())); HttpRequest->SetVerb(TEXT("GET")); HttpRequest->ProcessRequest(); } return std::to_string(_creditImages.Num() - 1); } void UScreenCreditsWidget::SetCredits( const FString& InCredits, const FString& InOnScreenCredits) { if (_numImagesLoading != 0) { _credits = InCredits; _onScreenCredits = InOnScreenCredits; return; } if (RichTextPopup) { RichTextPopup->SetText(FText::FromString(InCredits)); } if (RichTextOnScreen) { RichTextOnScreen->SetText(FText::FromString(InOnScreenCredits)); } }