From e6322c0088a0d3379fdfd94724a4d043f833f7d8 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 27 Apr 2026 15:23:01 +0200 Subject: [PATCH] Launch previewed guidance from dashboard --- .../HyperTwistCoachDashboardWidget.cpp | 141 ++++++++++++++++++ .../HyperTwistCoachDashboardWidget.h | 25 ++++ 2 files changed, 166 insertions(+) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 64b1819..3e9453b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -367,6 +367,62 @@ bool UHyperTwistCoachDashboardWidget::SelectNextGuidancePreviewCase() return true; } +FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartActiveGuidancePreviewDeckRun() +{ + const FHyperTwistTrainingDeck* Deck = GetActiveGuidancePreviewDeck(); + if (Deck == nullptr || !Deck->IsStructurallyValid()) + { + UpdateDashboardPresentation(); + return FHyperTwistTrainingRunState(); + } + + const FString SessionSuffix = + ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended + ? TEXT("_preview_recommended") + : (ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp + ? TEXT("_preview_follow_up") + : TEXT("_preview")); + const FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck( + *Deck, + DefaultUserId, + DefaultSessionId + SessionSuffix, + DefaultMode + ); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); + return RunState; +} + +FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartSelectedGuidancePreviewCaseRun() +{ + const FHyperTwistTrainingDeck Deck = BuildSelectedGuidancePreviewCaseDeck(); + if (!Deck.IsStructurallyValid()) + { + UpdateDashboardPresentation(); + return FHyperTwistTrainingRunState(); + } + + const FString SessionSuffix = + ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended + ? TEXT("_preview_case_recommended") + : (ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp + ? TEXT("_preview_case_follow_up") + : TEXT("_preview_case")); + const FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck( + Deck, + DefaultUserId, + DefaultSessionId + SessionSuffix, + DefaultMode + ); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); + return RunState; +} + FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::RestartRetainedRunRecapDeck() { if (!RetainedRunRecapDeck.IsStructurallyValid()) @@ -714,6 +770,16 @@ void UHyperTwistCoachDashboardWidget::HandleNextGuidanceCaseClicked() SelectNextGuidancePreviewCase(); } +void UHyperTwistCoachDashboardWidget::HandleStartPreviewDeckClicked() +{ + StartActiveGuidancePreviewDeckRun(); +} + +void UHyperTwistCoachDashboardWidget::HandleStartPreviewCaseClicked() +{ + StartSelectedGuidancePreviewCaseRun(); +} + void UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked() { CompleteActiveTrainingRun(false); @@ -1118,6 +1184,28 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() NextGuidanceCaseLabelRaw ); NextGuidanceCaseButtonLabel = NextGuidanceCaseLabelRaw; + + UTextBlock* StartPreviewDeckLabelRaw = nullptr; + StartPreviewDeckButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + GuidancePreviewActionRow, + TEXT("StartPreviewDeckButton"), + TEXT("StartPreviewDeckButtonLabel"), + TEXT("Start Preview Deck"), + StartPreviewDeckLabelRaw + ); + StartPreviewDeckButtonLabel = StartPreviewDeckLabelRaw; + + UTextBlock* StartPreviewCaseLabelRaw = nullptr; + StartPreviewCaseButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + GuidancePreviewActionRow, + TEXT("StartPreviewCaseButton"), + TEXT("StartPreviewCaseButtonLabel"), + TEXT("Start Selected Preview Case"), + StartPreviewCaseLabelRaw + ); + StartPreviewCaseButtonLabel = StartPreviewCaseLabelRaw; } LastAttemptTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( WidgetTree, @@ -1664,6 +1752,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() { NextGuidanceCaseButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleNextGuidanceCaseClicked); } + if (StartPreviewDeckButton != nullptr) + { + StartPreviewDeckButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartPreviewDeckClicked); + } + if (StartPreviewCaseButton != nullptr) + { + StartPreviewCaseButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartPreviewCaseClicked); + } if (CompleteRunButton != nullptr) { CompleteRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked); @@ -1861,6 +1957,32 @@ const FHyperTwistTrainingDeck* UHyperTwistCoachDashboardWidget::GetActiveGuidanc } } +FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildSelectedGuidancePreviewCaseDeck() const +{ + const FHyperTwistTrainingDeck* Deck = GetActiveGuidancePreviewDeck(); + if (Deck == nullptr + || !Deck->IsStructurallyValid() + || SelectedGuidancePreviewCaseIndex < 0 + || SelectedGuidancePreviewCaseIndex >= Deck->Cases.Num()) + { + return FHyperTwistTrainingDeck(); + } + + FHyperTwistTrainingDeck SingleCaseDeck = *Deck; + const FHyperTwistTrainingCase SelectedCase = Deck->Cases[SelectedGuidancePreviewCaseIndex]; + SingleCaseDeck.Cases = { SelectedCase }; + SingleCaseDeck.DeckId = FString::Printf( + TEXT("%s_case_%s"), + Deck->DeckId.IsEmpty() ? TEXT("preview") : *Deck->DeckId, + SelectedCase.CaseId.IsEmpty() ? TEXT("selected") : *SelectedCase.CaseId + ); + SingleCaseDeck.Title = FString::Printf( + TEXT("%s (Selected Case)"), + Deck->Title.IsEmpty() ? TEXT("Preview Guidance") : *Deck->Title + ); + return SingleCaseDeck; +} + void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid() @@ -2220,6 +2342,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() SelectedGuidancePreviewCase->CanonicalNotation.IsEmpty() ? TEXT("n/a") : *SelectedGuidancePreviewCase->CanonicalNotation ) : TEXT("Preview case: none selected yet."); + const bool bCanStartPreviewDeck = + !bHasLiveAttemptTimer && !bHasActiveRun && ActivePreviewDeck != nullptr && ActivePreviewDeck->IsStructurallyValid(); + const bool bCanStartPreviewCase = bCanStartPreviewDeck && SelectedGuidancePreviewCase != nullptr; if (HeadlineTextBlock != nullptr) { @@ -2449,6 +2574,22 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ActivePreviewDeck != nullptr && ActivePreviewDeck->Cases.Num() > 1 ); } + if (StartPreviewDeckButtonLabel != nullptr) + { + StartPreviewDeckButtonLabel->SetText(FText::FromString(TEXT("Start Preview Deck"))); + } + if (StartPreviewDeckButton != nullptr) + { + StartPreviewDeckButton->SetIsEnabled(bCanStartPreviewDeck); + } + if (StartPreviewCaseButtonLabel != nullptr) + { + StartPreviewCaseButtonLabel->SetText(FText::FromString(TEXT("Start Selected Preview Case"))); + } + if (StartPreviewCaseButton != nullptr) + { + StartPreviewCaseButton->SetIsEnabled(bCanStartPreviewCase); + } 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 b28b1d7..1cb82d8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -116,6 +116,12 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") bool SelectNextGuidancePreviewCase(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingRunState StartActiveGuidancePreviewDeckRun(); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingRunState StartSelectedGuidancePreviewCaseRun(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") FHyperTwistTrainingRunState RestartRetainedRunRecapDeck(); @@ -257,6 +263,18 @@ protected: UPROPERTY(Transient) TObjectPtr NextGuidanceCaseButtonLabel = nullptr; + UPROPERTY(Transient) + TObjectPtr StartPreviewDeckButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartPreviewDeckButtonLabel = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartPreviewCaseButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartPreviewCaseButtonLabel = nullptr; + UPROPERTY(Transient) TObjectPtr LastAttemptTextBlock = nullptr; @@ -509,6 +527,12 @@ protected: UFUNCTION() void HandleNextGuidanceCaseClicked(); + UFUNCTION() + void HandleStartPreviewDeckClicked(); + + UFUNCTION() + void HandleStartPreviewCaseClicked(); + UFUNCTION() void HandleCompleteRunClicked(); @@ -579,6 +603,7 @@ private: const FHyperTwistTrainingCoachSessionQueueStateEntry* FindSelectedQueueEntry() const; const FHyperTwistTrainingTimingPolicy* FindActiveTimingPolicy() const; const FHyperTwistTrainingDeck* GetActiveGuidancePreviewDeck() const; + FHyperTwistTrainingDeck BuildSelectedGuidancePreviewCaseDeck() const; bool IsLiveAttemptInspectionPhase() const; int32 GetMaxManualSplitCaptureCount() const; const FHyperTwistTrainingSplitPhaseDefinition* FindNextSplitPhaseToCapture() const;