diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 20dfe16..bed72b3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -303,6 +303,50 @@ bool UHyperTwistCoachDashboardWidget::SelectNextAttemptReview() return true; } +FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::RestartRetainedRunRecapDeck() +{ + if (!RetainedRunRecapDeck.IsStructurallyValid()) + { + UpdateDashboardPresentation(); + return FHyperTwistTrainingRunState(); + } + + const FString BaseSessionId = RetainedRunRecapSessionId.IsEmpty() + ? DefaultSessionId + : RetainedRunRecapSessionId; + const FString RestartSessionId = FString::Printf( + TEXT("%s_repeat_%s"), + *BaseSessionId, + *FGuid::NewGuid().ToString(EGuidFormats::Digits) + ); + const FString RestartUserId = RetainedRunRecapUserId.IsEmpty() + ? DefaultUserId + : RetainedRunRecapUserId; + + const FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck( + RetainedRunRecapDeck, + RestartUserId, + RestartSessionId, + RetainedRunRecapMode + ); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); + return RunState; +} + +void UHyperTwistCoachDashboardWidget::ClearRetainedRunRecap() +{ + bHasRetainedRunRecap = false; + RetainedRunRecapSummary = FHyperTwistTrainingSessionSummary(); + RetainedRunRecapDeck = FHyperTwistTrainingDeck(); + RetainedRunRecapUserId.Reset(); + RetainedRunRecapSessionId.Reset(); + RetainedRunRecapMode = EHyperTwistTrainingDeliveryMode::Timer; + UpdateDashboardPresentation(); +} + FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitSyntheticCurrentAttempt( const EHyperTwistTrainingAttemptResult Result, const int32 TimeMs @@ -549,6 +593,16 @@ void UHyperTwistCoachDashboardWidget::HandlePromoteClicked() PromoteFirstDeferredQueueEntry(); } +void UHyperTwistCoachDashboardWidget::HandleRepeatRunClicked() +{ + RestartRetainedRunRecapDeck(); +} + +void UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked() +{ + ClearRetainedRunRecap(); +} + void UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked() { CompleteActiveTrainingRun(false); @@ -780,6 +834,39 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() TEXT("CoachRunRecapDetail"), 2 ); + UHorizontalBox* RunRecapActionRow = WidgetTree->ConstructWidget( + UHorizontalBox::StaticClass(), + TEXT("CoachDashboardRunRecapActions") + ); + if (RunRecapActionRow != nullptr) + { + if (UVerticalBoxSlot* RunRecapActionRowSlot = RootLayout->AddChildToVerticalBox(RunRecapActionRow)) + { + RunRecapActionRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f)); + } + + UTextBlock* RepeatRunLabelRaw = nullptr; + RepeatRunButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + RunRecapActionRow, + TEXT("RepeatRunButton"), + TEXT("RepeatRunButtonLabel"), + TEXT("Repeat Run"), + RepeatRunLabelRaw + ); + RepeatRunButtonLabel = RepeatRunLabelRaw; + + UTextBlock* DismissRecapLabelRaw = nullptr; + DismissRecapButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + RunRecapActionRow, + TEXT("DismissRecapButton"), + TEXT("DismissRecapButtonLabel"), + TEXT("Dismiss Recap"), + DismissRecapLabelRaw + ); + DismissRecapButtonLabel = DismissRecapLabelRaw; + } LastAttemptTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( WidgetTree, RootLayout, @@ -1289,6 +1376,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() { PromoteButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePromoteClicked); } + if (RepeatRunButton != nullptr) + { + RepeatRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleRepeatRunClicked); + } + if (DismissRecapButton != nullptr) + { + DismissRecapButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked); + } if (CompleteRunButton != nullptr) { CompleteRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked); @@ -1450,6 +1545,10 @@ void UHyperTwistCoachDashboardWidget::SyncRetainedRunRecap() { RetainedRunRecapSummary = CachedRunSummary; bHasRetainedRunRecap = !RetainedRunRecapSummary.TrainingSessionId.IsEmpty(); + RetainedRunRecapDeck = CachedRunState.ActiveDeck; + RetainedRunRecapUserId = CachedRunState.Session.UserId; + RetainedRunRecapSessionId = CachedRunState.Session.TrainingSessionId; + RetainedRunRecapMode = CachedRunState.Session.Mode; } } @@ -1665,6 +1764,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() const FString RunRecapSourceLabel = bHasCurrentRunRecap ? TEXT("current run") : TEXT("last completed run"); + const bool bCanRepeatDisplayedRun = !bHasLiveAttemptTimer + && RetainedRunRecapDeck.IsStructurallyValid() + && (!bHasActiveRun + || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed + || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted); + const bool bCanDismissStoredRecap = !bHasCurrentRunRecap && bHasRetainedRunRecap; if (HeadlineTextBlock != nullptr) { @@ -1778,6 +1883,22 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() )); } } + if (RepeatRunButtonLabel != nullptr) + { + RepeatRunButtonLabel->SetText(FText::FromString(TEXT("Repeat Run"))); + } + if (RepeatRunButton != nullptr) + { + RepeatRunButton->SetIsEnabled(bCanRepeatDisplayedRun); + } + if (DismissRecapButtonLabel != nullptr) + { + DismissRecapButtonLabel->SetText(FText::FromString(TEXT("Dismiss Recap"))); + } + if (DismissRecapButton != nullptr) + { + DismissRecapButton->SetIsEnabled(bCanDismissStoredRecap); + } 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 8e59658..6a8013c 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") + FHyperTwistTrainingRunState RestartRetainedRunRecapDeck(); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + void ClearRetainedRunRecap(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") FHyperTwistTrainingRunStepResult SubmitSyntheticCurrentAttempt( EHyperTwistTrainingAttemptResult Result, @@ -157,6 +163,18 @@ protected: UPROPERTY(Transient) TObjectPtr RunRecapDetailTextBlock = nullptr; + UPROPERTY(Transient) + TObjectPtr RepeatRunButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr RepeatRunButtonLabel = nullptr; + + UPROPERTY(Transient) + TObjectPtr DismissRecapButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr DismissRecapButtonLabel = nullptr; + UPROPERTY(Transient) TObjectPtr LastAttemptTextBlock = nullptr; @@ -382,6 +400,12 @@ protected: UFUNCTION() void HandlePromoteClicked(); + UFUNCTION() + void HandleRepeatRunClicked(); + + UFUNCTION() + void HandleDismissRecapClicked(); + UFUNCTION() void HandleCompleteRunClicked(); @@ -461,4 +485,8 @@ private: int32 LastObservedAttemptCount = INDEX_NONE; bool bHasRetainedRunRecap = false; FHyperTwistTrainingSessionSummary RetainedRunRecapSummary; + FHyperTwistTrainingDeck RetainedRunRecapDeck; + FString RetainedRunRecapUserId; + FString RetainedRunRecapSessionId; + EHyperTwistTrainingDeliveryMode RetainedRunRecapMode = EHyperTwistTrainingDeliveryMode::Timer; };