// © 2025 Naked People Team. All Rights Reserved. #include "ForumCommissionWidget.h" #include "Components/Button.h" #include "CommonTextBlock.h" #include "NakedDesire/Commissions/Commission.h" #include "NakedDesire/Commissions/CommissionObjective.h" #include "NakedDesire/Commissions/CommissionTypes.h" #define LOCTEXT_NAMESPACE "ForumCommission" void UForumCommissionWidget::NativeOnInitialized() { Super::NativeOnInitialized(); if (AcceptButton) AcceptButton->OnClicked.AddUniqueDynamic(this, &UForumCommissionWidget::HandleAcceptClicked); if (AbandonButton) AbandonButton->OnClicked.AddUniqueDynamic(this, &UForumCommissionWidget::HandleAbandonClicked); } void UForumCommissionWidget::SetCommission(UCommission* InCommission) { Commission = InCommission; CachedObjectivesString.Reset(); // force a fresh push for the (possibly reused) row TimeSinceProgressRefresh = 0.0f; if (!Commission) return; if (TitleText) TitleText->SetText(Commission->GetTitle()); if (PosterText) PosterText->SetText(FText::Format(LOCTEXT("PosterFmt", "by {0}"), Commission->GetPosterUsername())); if (RewardText) RewardText->SetText(FormatReward(Commission->GetReward())); UpdateObjectivesText(); // Accept only offers; abandon only commitments. Completed / expired rows show neither control. const ECommissionState State = Commission->GetState(); if (AcceptButton) AcceptButton->SetVisibility(State == ECommissionState::Offered ? ESlateVisibility::Visible : ESlateVisibility::Collapsed); if (AbandonButton) AbandonButton->SetVisibility(State == ECommissionState::Accepted ? ESlateVisibility::Visible : ESlateVisibility::Collapsed); } void UForumCommissionWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) { Super::NativeTick(MyGeometry, InDeltaTime); // Only accepted rows have live progress; offered / completed / expired rows are static. if (!Commission || Commission->GetState() != ECommissionState::Accepted) return; TimeSinceProgressRefresh += InDeltaTime; if (TimeSinceProgressRefresh < ProgressRefreshInterval) return; TimeSinceProgressRefresh = 0.0f; UpdateObjectivesText(); } void UForumCommissionWidget::HandleAcceptClicked() { if (Commission) OnAcceptClicked.ExecuteIfBound(Commission); } void UForumCommissionWidget::HandleAbandonClicked() { if (Commission) OnAbandonClicked.ExecuteIfBound(Commission); } void UForumCommissionWidget::UpdateObjectivesText() { if (!DescriptionText) return; FString NewText = BuildObjectivesString(); if (NewText == CachedObjectivesString) return; // nothing changed since the last refresh — skip the SetText churn CachedObjectivesString = MoveTemp(NewText); DescriptionText->SetText(FText::FromString(CachedObjectivesString)); } FString UForumCommissionWidget::BuildObjectivesString() const { FString Result; if (!Commission) return Result; for (const UCommissionObjective* Objective : Commission->GetObjectives()) { if (!Objective) continue; if (!Result.IsEmpty()) Result += LINE_TERMINATOR; const int32 Pct = FMath::RoundToInt(Objective->GetProgress() * 100.0f); Result += FString::Printf(TEXT("• %s (%d%%)"), *Objective->GetDescription().ToString(), Pct); } return Result; } FText UForumCommissionWidget::FormatReward(const FCommissionReward& Reward) { TArray Parts; if (Reward.Money != 0) Parts.Add(FString::Printf(TEXT("¥%d"), Reward.Money)); // ¥ = yen sign if (!FMath::IsNearlyZero(Reward.XP)) Parts.Add(FString::Printf(TEXT("%.0f XP"), Reward.XP)); if (Reward.Followers != 0) Parts.Add(FString::Printf(TEXT("+%d followers"), Reward.Followers)); return FText::FromString(FString::Join(Parts, TEXT(" "))); } #undef LOCTEXT_NAMESPACE