Add attempt review to coach dashboard
This commit is contained in:
parent
aa340523ba
commit
840672e714
2 changed files with 200 additions and 28 deletions
|
|
@ -141,6 +141,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
|
|||
RefreshLiveAttemptClock();
|
||||
}
|
||||
SyncSelectedQueueEntry();
|
||||
SyncSelectedAttempt();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
|
|
@ -256,6 +257,47 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartSelectedQueueE
|
|||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::SelectPreviousAttemptReview()
|
||||
{
|
||||
if (CachedRunState.Attempts.Num() <= 0)
|
||||
{
|
||||
SelectedAttemptIndex = INDEX_NONE;
|
||||
LastObservedAttemptCount = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SelectedAttemptIndex == INDEX_NONE)
|
||||
{
|
||||
SyncSelectedAttempt();
|
||||
return SelectedAttemptIndex != INDEX_NONE;
|
||||
}
|
||||
|
||||
SelectedAttemptIndex =
|
||||
(SelectedAttemptIndex - 1 + CachedRunState.Attempts.Num()) % CachedRunState.Attempts.Num();
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::SelectNextAttemptReview()
|
||||
{
|
||||
if (CachedRunState.Attempts.Num() <= 0)
|
||||
{
|
||||
SelectedAttemptIndex = INDEX_NONE;
|
||||
LastObservedAttemptCount = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SelectedAttemptIndex == INDEX_NONE)
|
||||
{
|
||||
SyncSelectedAttempt();
|
||||
return SelectedAttemptIndex != INDEX_NONE;
|
||||
}
|
||||
|
||||
SelectedAttemptIndex = (SelectedAttemptIndex + 1) % CachedRunState.Attempts.Num();
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitSyntheticCurrentAttempt(
|
||||
const EHyperTwistTrainingAttemptResult Result,
|
||||
const int32 TimeMs
|
||||
|
|
@ -635,11 +677,21 @@ void UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked()
|
|||
SelectPreviousQueueEntry();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePreviousAttemptClicked()
|
||||
{
|
||||
SelectPreviousAttemptReview();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleStartSelectedClicked()
|
||||
{
|
||||
StartSelectedQueueEntry();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleNextAttemptClicked()
|
||||
{
|
||||
SelectNextAttemptReview();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleNextQueueClicked()
|
||||
{
|
||||
SelectNextQueueEntry();
|
||||
|
|
@ -1110,6 +1162,40 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
RecordDnfButtonLabel = RecordDnfLabelRaw;
|
||||
}
|
||||
|
||||
UHorizontalBox* AttemptReviewRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardAttemptReviewButtons")
|
||||
);
|
||||
if (AttemptReviewRow != nullptr)
|
||||
{
|
||||
if (UVerticalBoxSlot* AttemptReviewRowSlot = RootLayout->AddChildToVerticalBox(AttemptReviewRow))
|
||||
{
|
||||
AttemptReviewRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
UTextBlock* PreviousAttemptLabelRaw = nullptr;
|
||||
PreviousAttemptButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
AttemptReviewRow,
|
||||
TEXT("PreviousAttemptButton"),
|
||||
TEXT("PreviousAttemptButtonLabel"),
|
||||
TEXT("Prev Attempt"),
|
||||
PreviousAttemptLabelRaw
|
||||
);
|
||||
PreviousAttemptButtonLabel = PreviousAttemptLabelRaw;
|
||||
|
||||
UTextBlock* NextAttemptLabelRaw = nullptr;
|
||||
NextAttemptButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
AttemptReviewRow,
|
||||
TEXT("NextAttemptButton"),
|
||||
TEXT("NextAttemptButtonLabel"),
|
||||
TEXT("Next Attempt"),
|
||||
NextAttemptLabelRaw
|
||||
);
|
||||
NextAttemptButtonLabel = NextAttemptLabelRaw;
|
||||
}
|
||||
|
||||
UHorizontalBox* QueueNavRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardQueueNavButtons")
|
||||
|
|
@ -1227,10 +1313,18 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
{
|
||||
PreviousQueueButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked);
|
||||
}
|
||||
if (PreviousAttemptButton != nullptr)
|
||||
{
|
||||
PreviousAttemptButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviousAttemptClicked);
|
||||
}
|
||||
if (StartSelectedButton != nullptr)
|
||||
{
|
||||
StartSelectedButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartSelectedClicked);
|
||||
}
|
||||
if (NextAttemptButton != nullptr)
|
||||
{
|
||||
NextAttemptButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleNextAttemptClicked);
|
||||
}
|
||||
if (NextQueueButton != nullptr)
|
||||
{
|
||||
NextQueueButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleNextQueueClicked);
|
||||
|
|
@ -1291,6 +1385,31 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedQueueEntry()
|
|||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::SyncSelectedAttempt()
|
||||
{
|
||||
if (CachedRunState.Attempts.Num() <= 0)
|
||||
{
|
||||
SelectedAttemptIndex = INDEX_NONE;
|
||||
LastObservedAttemptCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bAttemptCountChanged = LastObservedAttemptCount != CachedRunState.Attempts.Num();
|
||||
const bool bSelectionStillValid =
|
||||
SelectedAttemptIndex >= 0 && SelectedAttemptIndex < CachedRunState.Attempts.Num();
|
||||
|
||||
if (bPreferLatestAttemptSelectionOnRefresh && bAttemptCountChanged)
|
||||
{
|
||||
SelectedAttemptIndex = CachedRunState.Attempts.Num() - 1;
|
||||
}
|
||||
else if (!bSelectionStillValid)
|
||||
{
|
||||
SelectedAttemptIndex = CachedRunState.Attempts.Num() - 1;
|
||||
}
|
||||
|
||||
LastObservedAttemptCount = CachedRunState.Attempts.Num();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
||||
{
|
||||
const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid()
|
||||
|
|
@ -1361,15 +1480,16 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
const FString VerificationNotePreview = CachedRepositoryRoundTripVerification.VerificationNotes.Num() > 0
|
||||
? CachedRepositoryRoundTripVerification.VerificationNotes[0]
|
||||
: TEXT("none");
|
||||
const FHyperTwistTrainingAttempt* LastAttempt = CachedRunState.Attempts.Num() > 0
|
||||
? &CachedRunState.Attempts.Last()
|
||||
const FHyperTwistTrainingAttempt* ReviewedAttempt =
|
||||
SelectedAttemptIndex >= 0 && SelectedAttemptIndex < CachedRunState.Attempts.Num()
|
||||
? &CachedRunState.Attempts[SelectedAttemptIndex]
|
||||
: nullptr;
|
||||
FString LastAttemptPhaseSummary = TEXT("none");
|
||||
if (LastAttempt != nullptr && LastAttempt->TimingBreakdown.DerivedPhaseTimings.Num() > 0)
|
||||
FString ReviewedAttemptPhaseSummary = TEXT("none");
|
||||
if (ReviewedAttempt != nullptr && ReviewedAttempt->TimingBreakdown.DerivedPhaseTimings.Num() > 0)
|
||||
{
|
||||
TArray<FString> PhaseParts;
|
||||
PhaseParts.Reserve(LastAttempt->TimingBreakdown.DerivedPhaseTimings.Num());
|
||||
for (const FHyperTwistPhaseTiming& PhaseTiming : LastAttempt->TimingBreakdown.DerivedPhaseTimings)
|
||||
PhaseParts.Reserve(ReviewedAttempt->TimingBreakdown.DerivedPhaseTimings.Num());
|
||||
for (const FHyperTwistPhaseTiming& PhaseTiming : ReviewedAttempt->TimingBreakdown.DerivedPhaseTimings)
|
||||
{
|
||||
PhaseParts.Add(FString::Printf(
|
||||
TEXT("%s %d-%d (%d ms)"),
|
||||
|
|
@ -1379,8 +1499,11 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
PhaseTiming.DurationMs
|
||||
));
|
||||
}
|
||||
LastAttemptPhaseSummary = FString::Join(PhaseParts, TEXT(" | "));
|
||||
ReviewedAttemptPhaseSummary = FString::Join(PhaseParts, TEXT(" | "));
|
||||
}
|
||||
const FString ReviewedAttemptOrdinal = ReviewedAttempt != nullptr
|
||||
? FString::Printf(TEXT("%d/%d"), SelectedAttemptIndex + 1, CachedRunState.Attempts.Num())
|
||||
: TEXT("none");
|
||||
const bool bHasSessionScopedLastStep = !LastStepResult.SessionSummary.TrainingSessionId.IsEmpty()
|
||||
&& LastStepResult.SessionSummary.TrainingSessionId == CachedRunState.Session.TrainingSessionId;
|
||||
const bool bLastStepCompletedRun = bHasSessionScopedLastStep
|
||||
|
|
@ -1542,59 +1665,60 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (LastAttemptTextBlock != nullptr)
|
||||
{
|
||||
if (bHasSessionScopedLastStep)
|
||||
if (ReviewedAttempt != nullptr)
|
||||
{
|
||||
LastAttemptTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Last recorded attempt: case %s | completed at %s | session completed attempts %d"),
|
||||
LastStepResult.SessionSummary.LastCaseId.IsEmpty() ? TEXT("n/a") : *LastStepResult.SessionSummary.LastCaseId,
|
||||
LastStepResult.SessionSummary.LastCompletedAtUtc.IsEmpty()
|
||||
TEXT("Attempt review: %s | case %s | result %s | completed at %s | latest submission: %s"),
|
||||
*ReviewedAttemptOrdinal,
|
||||
ReviewedAttempt->CaseId.IsEmpty() ? TEXT("n/a") : *ReviewedAttempt->CaseId,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ReviewedAttempt->Result),
|
||||
ReviewedAttempt->CompletedAtUtc.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *LastStepResult.SessionSummary.LastCompletedAtUtc,
|
||||
LastStepResult.SessionSummary.CompletedAttemptCount
|
||||
: *ReviewedAttempt->CompletedAtUtc,
|
||||
bHasSessionScopedLastStep ? TEXT("yes") : TEXT("no")
|
||||
)));
|
||||
}
|
||||
else
|
||||
{
|
||||
LastAttemptTextBlock->SetText(FText::FromString(
|
||||
TEXT("Last recorded attempt: none in this dashboard session yet.")
|
||||
TEXT("Attempt review: no attempts recorded in the active run yet.")
|
||||
));
|
||||
}
|
||||
}
|
||||
if (LastAttemptTimingTextBlock != nullptr)
|
||||
{
|
||||
if (LastAttempt != nullptr)
|
||||
if (ReviewedAttempt != nullptr)
|
||||
{
|
||||
LastAttemptTimingTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Last timing: result %s | inspection %d ms | raw solve %d ms | final %d ms | penalty %s | split captures %d | did-not-finish: %s"),
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(LastAttempt->Result),
|
||||
LastAttempt->TimingBreakdown.InspectionElapsedMs,
|
||||
LastAttempt->TimingBreakdown.RawSolveTimeMs,
|
||||
LastAttempt->TimingBreakdown.FinalTimeMs,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(LastAttempt->TimingBreakdown.AppliedPenalty),
|
||||
LastAttempt->TimingBreakdown.SplitCaptures.Num(),
|
||||
LastAttempt->TimingBreakdown.bDidNotFinish ? TEXT("yes") : TEXT("no")
|
||||
TEXT("Reviewed timing: inspection %d ms | raw solve %d ms | final %d ms | penalty %s | split captures %d | did-not-finish: %s"),
|
||||
ReviewedAttempt->TimingBreakdown.InspectionElapsedMs,
|
||||
ReviewedAttempt->TimingBreakdown.RawSolveTimeMs,
|
||||
ReviewedAttempt->TimingBreakdown.FinalTimeMs,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ReviewedAttempt->TimingBreakdown.AppliedPenalty),
|
||||
ReviewedAttempt->TimingBreakdown.SplitCaptures.Num(),
|
||||
ReviewedAttempt->TimingBreakdown.bDidNotFinish ? TEXT("yes") : TEXT("no")
|
||||
)));
|
||||
}
|
||||
else
|
||||
{
|
||||
LastAttemptTimingTextBlock->SetText(FText::FromString(
|
||||
TEXT("Last timing: unavailable until the active run records an attempt.")
|
||||
TEXT("Reviewed timing: unavailable until the active run records an attempt.")
|
||||
));
|
||||
}
|
||||
}
|
||||
if (LastAttemptPhaseTextBlock != nullptr)
|
||||
{
|
||||
if (LastAttempt != nullptr)
|
||||
if (ReviewedAttempt != nullptr)
|
||||
{
|
||||
LastAttemptPhaseTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Last derived phases: %s"),
|
||||
*LastAttemptPhaseSummary
|
||||
TEXT("Reviewed derived phases: %s"),
|
||||
*ReviewedAttemptPhaseSummary
|
||||
)));
|
||||
}
|
||||
else
|
||||
{
|
||||
LastAttemptPhaseTextBlock->SetText(FText::FromString(
|
||||
TEXT("Last derived phases: none yet.")
|
||||
TEXT("Reviewed derived phases: none yet.")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -2101,6 +2225,22 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
UndoSplitButton->SetIsEnabled(bLiveSolvePhase && LiveAttemptSplitCaptures.Num() > 0);
|
||||
}
|
||||
if (PreviousAttemptButtonLabel != nullptr)
|
||||
{
|
||||
PreviousAttemptButtonLabel->SetText(FText::FromString(TEXT("Prev Attempt")));
|
||||
}
|
||||
if (PreviousAttemptButton != nullptr)
|
||||
{
|
||||
PreviousAttemptButton->SetIsEnabled(CachedRunState.Attempts.Num() > 1);
|
||||
}
|
||||
if (NextAttemptButtonLabel != nullptr)
|
||||
{
|
||||
NextAttemptButtonLabel->SetText(FText::FromString(TEXT("Next Attempt")));
|
||||
}
|
||||
if (NextAttemptButton != nullptr)
|
||||
{
|
||||
NextAttemptButton->SetIsEnabled(CachedRunState.Attempts.Num() > 1);
|
||||
}
|
||||
if (PreviousQueueButtonLabel != nullptr)
|
||||
{
|
||||
PreviousQueueButtonLabel->SetText(FText::FromString(TEXT("Prev Queue")));
|
||||
|
|
|
|||
|
|
@ -46,9 +46,15 @@ public:
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool bRefreshLiveAttemptClockOnTick = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool bPreferLatestAttemptSelectionOnRefresh = true;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 SelectedQueueEntryIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 SelectedAttemptIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool bHasLiveAttemptTimer = false;
|
||||
|
||||
|
|
@ -85,6 +91,12 @@ public:
|
|||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
FHyperTwistTrainingRunState StartSelectedQueueEntry();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool SelectPreviousAttemptReview();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool SelectNextAttemptReview();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
FHyperTwistTrainingRunStepResult SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult Result,
|
||||
|
|
@ -325,12 +337,24 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> PreviousQueueButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> PreviousAttemptButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> PreviousAttemptButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> StartSelectedButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> StartSelectedButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> NextAttemptButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> NextAttemptButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> NextQueueButton = nullptr;
|
||||
|
||||
|
|
@ -391,15 +415,22 @@ protected:
|
|||
UFUNCTION()
|
||||
void HandlePreviousQueueClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePreviousAttemptClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleStartSelectedClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleNextAttemptClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleNextQueueClicked();
|
||||
|
||||
private:
|
||||
void EnsureDefaultDashboardBuilt();
|
||||
void SyncSelectedQueueEntry();
|
||||
void SyncSelectedAttempt();
|
||||
void UpdateDashboardPresentation();
|
||||
void RefreshLiveAttemptClock();
|
||||
FString BuildDefaultDeferredUntilUtc() const;
|
||||
|
|
@ -417,4 +448,5 @@ private:
|
|||
double LiveAttemptStartedAtSeconds = 0.0;
|
||||
double LiveAttemptSolveStartedAtSeconds = 0.0;
|
||||
int32 LiveAttemptStoredInspectionElapsedMs = 0;
|
||||
int32 LastObservedAttemptCount = INDEX_NONE;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue