#include "UILoadingScreen_Base.h" #include "Components/ProgressBar.h" #include "CommonTextBlock.h" #include "Engine/DataTable.h" #include "Engine/World.h" #include "TimerManager.h" #include "UILoadingProfilesDataAsset.h" #include "UILoadingTipRow.h" void UUILoadingScreen_Base::Configure(EZMMOLoadingContext InContext, UZMMOLoadingProfilesDataAsset* Profiles, UDataTable* TipsTable) { Context_ = InContext; bCompleteFired_ = false; // Perfil (etapas + título). Profile_ = (Profiles != nullptr) ? Profiles->GetProfile(InContext) : FZMMOLoadingProfile(); StepStatus_.Reset(); for (const FZMMOLoadingStepDef& Step : Profile_.Steps) { StepStatus_.Add(Step.StepId, EZMMOLoadingStepStatus::Pending); } // Pool de dicas filtrada por contexto (None = qualquer). TipPool_.Reset(); if (TipsTable != nullptr) { TipsTable->ForeachRow(TEXT("UUILoadingScreen_Base::Configure"), [this](const FName& /*Key*/, const FZMMOLoadingTipRow& Row) { if (Row.Context == EZMMOLoadingContext::None || Row.Context == Context_) { if (!Row.Tip.IsEmpty()) { TipPool_.Add(Row.Tip); } } }); } NextTipIndex_ = 0; RefreshUI(); if (IsActivated()) { StartTipTimer(); } } void UUILoadingScreen_Base::NativeOnActivated() { Super::NativeOnActivated(); RefreshUI(); StartTipTimer(); } void UUILoadingScreen_Base::NativeOnDeactivated() { StopTipTimer(); Super::NativeOnDeactivated(); } void UUILoadingScreen_Base::MarkStepRunning(FName StepId) { if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId)) { // Já Done não regride. if (*Status != EZMMOLoadingStepStatus::Done && *Status != EZMMOLoadingStepStatus::Failed) { *Status = EZMMOLoadingStepStatus::Running; RefreshUI(); } } } void UUILoadingScreen_Base::MarkStepDone(FName StepId) { if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId)) { *Status = EZMMOLoadingStepStatus::Done; RefreshUI(); if (!bCompleteFired_ && AreAllStepsDone()) { bCompleteFired_ = true; OnLoadingComplete.Broadcast(); } } } void UUILoadingScreen_Base::MarkStepFailed(FName StepId, const FText& Reason) { if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId)) { *Status = EZMMOLoadingStepStatus::Failed; if (Text_Status && !Reason.IsEmpty()) { Text_Status->SetText(Reason); } RefreshUI(); } } bool UUILoadingScreen_Base::AreAllStepsDone() const { if (Profile_.Steps.Num() == 0) { return false; } for (const FZMMOLoadingStepDef& Step : Profile_.Steps) { const EZMMOLoadingStepStatus* Status = StepStatus_.Find(Step.StepId); if (Status == nullptr || *Status != EZMMOLoadingStepStatus::Done) { return false; } } return true; } int32 UUILoadingScreen_Base::FindStepIndex(FName StepId) const { for (int32 i = 0; i < Profile_.Steps.Num(); ++i) { if (Profile_.Steps[i].StepId == StepId) { return i; } } return INDEX_NONE; } EZMMOLoadingStepStatus UUILoadingScreen_Base::GetStepStatus(int32 Index) const { if (!Profile_.Steps.IsValidIndex(Index)) return EZMMOLoadingStepStatus::Pending; const EZMMOLoadingStepStatus* Status = StepStatus_.Find(Profile_.Steps[Index].StepId); return Status ? *Status : EZMMOLoadingStepStatus::Pending; } FText UUILoadingScreen_Base::ComputeCurrentStatusText() const { // Prioridade: Running > último Done > primeiro Pending > Status default. int32 LastDone = INDEX_NONE; for (int32 i = 0; i < Profile_.Steps.Num(); ++i) { const EZMMOLoadingStepStatus St = GetStepStatus(i); if (St == EZMMOLoadingStepStatus::Running) { return Profile_.Steps[i].Label; } if (St == EZMMOLoadingStepStatus::Done) { LastDone = i; } } if (LastDone != INDEX_NONE) { return Profile_.Steps[LastDone].Label; } if (Profile_.Steps.Num() > 0) { return Profile_.Steps[0].Label; } return StatusDefault; } float UUILoadingScreen_Base::ComputeProgress01() const { const int32 N = Profile_.Steps.Num(); if (N == 0) return 0.f; float Acc = 0.f; for (int32 i = 0; i < N; ++i) { const EZMMOLoadingStepStatus St = GetStepStatus(i); if (St == EZMMOLoadingStepStatus::Done) Acc += 1.f; else if (St == EZMMOLoadingStepStatus::Running) Acc += 0.5f; } return FMath::Clamp(Acc / static_cast(N), 0.f, 1.f); } void UUILoadingScreen_Base::RefreshUI() { if (Text_Title) { const FText& Title = Profile_.TitleOverride.IsEmpty() ? TitleDefault : Profile_.TitleOverride; Text_Title->SetText(Title); } if (Text_Status) { Text_Status->SetText(ComputeCurrentStatusText()); } if (ProgressBar_Progress) { ProgressBar_Progress->SetPercent(ComputeProgress01()); } if (Text_Tip && TipPool_.Num() > 0 && Text_Tip->GetText().IsEmpty()) { // Primeira dica na inicialização (timer cuida das próximas). Text_Tip->SetText(TipPool_[0]); NextTipIndex_ = TipPool_.Num() > 1 ? 1 : 0; } } void UUILoadingScreen_Base::RotateTip() { if (!Text_Tip || TipPool_.Num() == 0) return; Text_Tip->SetText(TipPool_[NextTipIndex_]); NextTipIndex_ = (NextTipIndex_ + 1) % TipPool_.Num(); } void UUILoadingScreen_Base::StartTipTimer() { StopTipTimer(); if (TipRotationIntervalSeconds <= 0.f || TipPool_.Num() <= 1) return; if (UWorld* W = GetWorld()) { W->GetTimerManager().SetTimer(TipTimerHandle_, this, &UUILoadingScreen_Base::RotateTip, TipRotationIntervalSeconds, /*bLoop=*/true); } } void UUILoadingScreen_Base::StopTipTimer() { if (UWorld* W = GetWorld()) { W->GetTimerManager().ClearTimer(TipTimerHandle_); } TipTimerHandle_.Invalidate(); }