diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index e070438..9452c8d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -303,6 +303,20 @@ bool UHyperTwistCoachDashboardWidget::SelectNextAttemptReview() return true; } +FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::PreviewRecommendedCoachDeck() +{ + const FHyperTwistTrainingDeck Deck = BuildActiveCoachRecommendedDeck(DefaultCoachMaxCases); + UpdateDashboardPresentation(); + return Deck; +} + +FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::PreviewFollowUpCoachDeck() +{ + const FHyperTwistTrainingDeck Deck = BuildActiveCoachFollowUpDeck(DefaultCoachMaxCases); + UpdateDashboardPresentation(); + return Deck; +} + FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::RestartRetainedRunRecapDeck() { if (!RetainedRunRecapDeck.IsStructurallyValid()) @@ -630,6 +644,16 @@ void UHyperTwistCoachDashboardWidget::HandleStartRecommendedRunClicked() UpdateDashboardPresentation(); } +void UHyperTwistCoachDashboardWidget::HandlePreviewRecommendedClicked() +{ + PreviewRecommendedCoachDeck(); +} + +void UHyperTwistCoachDashboardWidget::HandlePreviewFollowUpClicked() +{ + PreviewFollowUpCoachDeck(); +} + void UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked() { CompleteActiveTrainingRun(false); @@ -873,6 +897,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() TEXT("CoachHandoffStatus"), 2 ); + GuidanceReviewHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachGuidanceReviewHeader"), + 2 + ); + RecommendedDeckPreviewTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachRecommendedDeckPreview"), + 2 + ); + FollowUpDeckPreviewTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachFollowUpDeckPreview"), + 2 + ); UHorizontalBox* RunRecapActionRow = WidgetTree->ConstructWidget( UHorizontalBox::StaticClass(), TEXT("CoachDashboardRunRecapActions") @@ -950,6 +992,39 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() ); StartRecommendedRunButtonLabel = StartRecommendedRunLabelRaw; } + UHorizontalBox* GuidancePreviewActionRow = WidgetTree->ConstructWidget( + UHorizontalBox::StaticClass(), + TEXT("CoachDashboardGuidancePreviewActions") + ); + if (GuidancePreviewActionRow != nullptr) + { + if (UVerticalBoxSlot* GuidancePreviewActionRowSlot = RootLayout->AddChildToVerticalBox(GuidancePreviewActionRow)) + { + GuidancePreviewActionRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f)); + } + + UTextBlock* PreviewRecommendedLabelRaw = nullptr; + PreviewRecommendedButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + GuidancePreviewActionRow, + TEXT("PreviewRecommendedButton"), + TEXT("PreviewRecommendedButtonLabel"), + TEXT("Preview Recommended"), + PreviewRecommendedLabelRaw + ); + PreviewRecommendedButtonLabel = PreviewRecommendedLabelRaw; + + UTextBlock* PreviewFollowUpLabelRaw = nullptr; + PreviewFollowUpButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + GuidancePreviewActionRow, + TEXT("PreviewFollowUpButton"), + TEXT("PreviewFollowUpButtonLabel"), + TEXT("Preview Follow-Up"), + PreviewFollowUpLabelRaw + ); + PreviewFollowUpButtonLabel = PreviewFollowUpLabelRaw; + } LastAttemptTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( WidgetTree, RootLayout, @@ -1479,6 +1554,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() { StartRecommendedRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartRecommendedRunClicked); } + if (PreviewRecommendedButton != nullptr) + { + PreviewRecommendedButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviewRecommendedClicked); + } + if (PreviewFollowUpButton != nullptr) + { + PreviewFollowUpButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviewFollowUpClicked); + } if (CompleteRunButton != nullptr) { CompleteRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked); @@ -1917,6 +2000,59 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() const FString RecommendedRunLabel = CachedCoachBrief.PrimaryActionLabel.IsEmpty() ? TEXT("Start Recommended") : CachedCoachBrief.PrimaryActionLabel; + const auto BuildDeckPreviewLine = [](const FHyperTwistTrainingDeck& Deck, const FHyperTwistCoachBrief& Brief, const TCHAR* EmptyPrefix) + { + if (Deck.IsStructurallyValid()) + { + TArray PreviewCaseParts; + const int32 PreviewCaseCount = FMath::Min(Deck.Cases.Num(), 3); + for (int32 CaseIndex = 0; CaseIndex < PreviewCaseCount; ++CaseIndex) + { + const FHyperTwistTrainingCase& PreviewCase = Deck.Cases[CaseIndex]; + PreviewCaseParts.Add( + PreviewCase.PromptLabel.IsEmpty() ? PreviewCase.CaseId : PreviewCase.PromptLabel + ); + } + const FString PreviewCases = PreviewCaseParts.Num() > 0 + ? FString::Join(PreviewCaseParts, TEXT(" | ")) + : TEXT("none"); + return FString::Printf( + TEXT("Deck preview: %s | Deck: %s | Cases: %d | Selection: %s | Difficulty: %s | Subset: %s | Preview: %s"), + Deck.Title.IsEmpty() ? TEXT("n/a") : *Deck.Title, + Deck.DeckId.IsEmpty() ? TEXT("n/a") : *Deck.DeckId, + Deck.Cases.Num(), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(Deck.SelectionPolicy), + Deck.DifficultyBand.IsEmpty() ? TEXT("n/a") : *Deck.DifficultyBand, + Deck.SubsetId.IsEmpty() ? TEXT("n/a") : *Deck.SubsetId, + *PreviewCases + ); + } + + if (!Brief.UserId.IsEmpty() && !Brief.Headline.IsEmpty()) + { + return FString::Printf( + TEXT("%s: %s | Deck: %s | Method: %s | Focus cases: %d | Action: %s"), + EmptyPrefix, + *Brief.Headline, + Brief.FocusDeckId.IsEmpty() ? TEXT("n/a") : *Brief.FocusDeckId, + Brief.FocusMethodSegmentId.IsEmpty() ? TEXT("n/a") : *Brief.FocusMethodSegmentId, + Brief.FocusCaseIds.Num(), + Brief.PrimaryActionLabel.IsEmpty() ? TEXT("n/a") : *Brief.PrimaryActionLabel + ); + } + + return FString::Printf(TEXT("%s: no guidance available"), EmptyPrefix); + }; + const FString RecommendedDeckPreviewLine = BuildDeckPreviewLine( + CachedCoachRecommendedDeck, + CachedCoachBrief, + TEXT("Recommended guidance") + ); + const FString FollowUpDeckPreviewLine = BuildDeckPreviewLine( + CachedCoachFollowUpDeck, + CachedCoachFollowUpBrief, + TEXT("Follow-up guidance") + ); if (HeadlineTextBlock != nullptr) { @@ -2054,6 +2190,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { CoachHandoffStatusTextBlock->SetText(FText::FromString(CoachHandoffStatusLine)); } + if (GuidanceReviewHeaderTextBlock != nullptr) + { + GuidanceReviewHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Review]"))); + } + if (RecommendedDeckPreviewTextBlock != nullptr) + { + RecommendedDeckPreviewTextBlock->SetText(FText::FromString(RecommendedDeckPreviewLine)); + } + if (FollowUpDeckPreviewTextBlock != nullptr) + { + FollowUpDeckPreviewTextBlock->SetText(FText::FromString(FollowUpDeckPreviewLine)); + } if (StartQueuedRunButtonLabel != nullptr) { StartQueuedRunButtonLabel->SetText(FText::FromString(QueuedRunLabel)); @@ -2084,6 +2232,28 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() bCanUseCoachHandoff && CachedCoachPanelState.bCanStartCoachRecommendedRun ); } + if (PreviewRecommendedButtonLabel != nullptr) + { + PreviewRecommendedButtonLabel->SetText(FText::FromString(TEXT("Preview Recommended"))); + } + if (PreviewRecommendedButton != nullptr) + { + PreviewRecommendedButton->SetIsEnabled( + !bHasLiveAttemptTimer && CachedCoachPanelState.bCanStartCoachRecommendedRun + ); + } + if (PreviewFollowUpButtonLabel != nullptr) + { + PreviewFollowUpButtonLabel->SetText(FText::FromString(TEXT("Preview Follow-Up"))); + } + if (PreviewFollowUpButton != nullptr) + { + PreviewFollowUpButton->SetIsEnabled( + !bHasLiveAttemptTimer + && CachedCoachPanelState.bCanStartCoachFollowUpRun + && CachedCoachPanelState.bHasFollowUpGuidance + ); + } if (LastAttemptTextBlock != nullptr) { if (ReviewedAttempt != nullptr) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index 6f9c924..06c8fd8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -97,6 +97,12 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") bool SelectNextAttemptReview(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingDeck PreviewRecommendedCoachDeck(); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingDeck PreviewFollowUpCoachDeck(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") FHyperTwistTrainingRunState RestartRetainedRunRecapDeck(); @@ -169,6 +175,15 @@ protected: UPROPERTY(Transient) TObjectPtr CoachHandoffStatusTextBlock = nullptr; + UPROPERTY(Transient) + TObjectPtr GuidanceReviewHeaderTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr RecommendedDeckPreviewTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr FollowUpDeckPreviewTextBlock = nullptr; + UPROPERTY(Transient) TObjectPtr RepeatRunButton = nullptr; @@ -199,6 +214,18 @@ protected: UPROPERTY(Transient) TObjectPtr StartRecommendedRunButtonLabel = nullptr; + UPROPERTY(Transient) + TObjectPtr PreviewRecommendedButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr PreviewRecommendedButtonLabel = nullptr; + + UPROPERTY(Transient) + TObjectPtr PreviewFollowUpButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr PreviewFollowUpButtonLabel = nullptr; + UPROPERTY(Transient) TObjectPtr LastAttemptTextBlock = nullptr; @@ -439,6 +466,12 @@ protected: UFUNCTION() void HandleStartRecommendedRunClicked(); + UFUNCTION() + void HandlePreviewRecommendedClicked(); + + UFUNCTION() + void HandlePreviewFollowUpClicked(); + UFUNCTION() void HandleCompleteRunClicked();