Add split capture to coach dashboard timer
This commit is contained in:
parent
1d72a8e7a5
commit
1251369cb7
2 changed files with 258 additions and 0 deletions
|
|
@ -134,6 +134,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
|
|||
LiveAttemptInspectionElapsedMs = 0;
|
||||
LiveAttemptSolveElapsedMs = 0;
|
||||
LiveAttemptDisplayedMs = 0;
|
||||
LiveAttemptSplitCaptures.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -320,6 +321,7 @@ bool UHyperTwistCoachDashboardWidget::StartLiveAttemptTimer()
|
|||
LiveAttemptInspectionElapsedMs = 0;
|
||||
LiveAttemptSolveElapsedMs = 0;
|
||||
LiveAttemptDisplayedMs = 0;
|
||||
LiveAttemptSplitCaptures.Reset();
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -349,6 +351,7 @@ void UHyperTwistCoachDashboardWidget::CancelLiveAttemptTimer()
|
|||
LiveAttemptInspectionElapsedMs = 0;
|
||||
LiveAttemptSolveElapsedMs = 0;
|
||||
LiveAttemptDisplayedMs = 0;
|
||||
LiveAttemptSplitCaptures.Reset();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
|
|
@ -380,6 +383,7 @@ FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitLiveTime
|
|||
Attempt.TimingBreakdown.InspectionElapsedMs = FMath::Max(LiveAttemptInspectionElapsedMs, 0);
|
||||
Attempt.TimingBreakdown.RawSolveTimeMs = FMath::Max(LiveAttemptSolveElapsedMs, 0);
|
||||
Attempt.TimingBreakdown.FinalTimeMs = FMath::Max(LiveAttemptSolveElapsedMs, 0);
|
||||
Attempt.TimingBreakdown.SplitCaptures = LiveAttemptSplitCaptures;
|
||||
Attempt.TimingBreakdown.bDidNotFinish =
|
||||
Result == EHyperTwistTrainingAttemptResult::Timeout
|
||||
|| Result == EHyperTwistTrainingAttemptResult::DNF
|
||||
|
|
@ -400,6 +404,43 @@ FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitLiveTime
|
|||
return StepResult;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::CaptureNextLiveAttemptSplit()
|
||||
{
|
||||
if (!bHasLiveAttemptTimer || LiveAttemptPhase != EHyperTwistCoachDashboardAttemptPhase::Solving)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSplitPhaseDefinition* NextPhaseDefinition = FindNextSplitPhaseToCapture();
|
||||
if (NextPhaseDefinition == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RefreshLiveAttemptClock();
|
||||
|
||||
FHyperTwistTrainingSplitCapture SplitCapture;
|
||||
SplitCapture.PhaseId = NextPhaseDefinition->PhaseId;
|
||||
SplitCapture.Label = NextPhaseDefinition->Label;
|
||||
SplitCapture.TimestampMs = FMath::Max(LiveAttemptSolveElapsedMs, 0);
|
||||
|
||||
LiveAttemptSplitCaptures.Add(SplitCapture);
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::UndoLastLiveAttemptSplit()
|
||||
{
|
||||
if (LiveAttemptSplitCaptures.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LiveAttemptSplitCaptures.RemoveAt(LiveAttemptSplitCaptures.Num() - 1);
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePrimaryActionClicked()
|
||||
{
|
||||
ExecutePrimaryCoachAction();
|
||||
|
|
@ -553,6 +594,16 @@ void UHyperTwistCoachDashboardWidget::HandleCancelTimerClicked()
|
|||
CancelLiveAttemptTimer();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleCaptureSplitClicked()
|
||||
{
|
||||
CaptureNextLiveAttemptSplit();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleUndoSplitClicked()
|
||||
{
|
||||
UndoLastLiveAttemptSplit();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked()
|
||||
{
|
||||
SelectPreviousQueueEntry();
|
||||
|
|
@ -665,6 +716,18 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTimerStatus"),
|
||||
2
|
||||
);
|
||||
SplitHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachSplitHeader"),
|
||||
2
|
||||
);
|
||||
SplitStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachSplitStatus"),
|
||||
8
|
||||
);
|
||||
FollowUpTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
|
|
@ -874,6 +937,40 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
CancelTimerButtonLabel = CancelTimerLabelRaw;
|
||||
}
|
||||
|
||||
UHorizontalBox* SplitCaptureRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardSplitCaptureRow")
|
||||
);
|
||||
if (SplitCaptureRow != nullptr)
|
||||
{
|
||||
if (UVerticalBoxSlot* SplitCaptureRowSlot = RootLayout->AddChildToVerticalBox(SplitCaptureRow))
|
||||
{
|
||||
SplitCaptureRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
UTextBlock* CaptureSplitLabelRaw = nullptr;
|
||||
CaptureSplitButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
SplitCaptureRow,
|
||||
TEXT("CaptureSplitButton"),
|
||||
TEXT("CaptureSplitButtonLabel"),
|
||||
TEXT("Capture Split"),
|
||||
CaptureSplitLabelRaw
|
||||
);
|
||||
CaptureSplitButtonLabel = CaptureSplitLabelRaw;
|
||||
|
||||
UTextBlock* UndoSplitLabelRaw = nullptr;
|
||||
UndoSplitButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
SplitCaptureRow,
|
||||
TEXT("UndoSplitButton"),
|
||||
TEXT("UndoSplitButtonLabel"),
|
||||
TEXT("Undo Split"),
|
||||
UndoSplitLabelRaw
|
||||
);
|
||||
UndoSplitButtonLabel = UndoSplitLabelRaw;
|
||||
}
|
||||
|
||||
UHorizontalBox* RunControlRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardRunControlButtons")
|
||||
|
|
@ -1053,6 +1150,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
{
|
||||
CancelTimerButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCancelTimerClicked);
|
||||
}
|
||||
if (CaptureSplitButton != nullptr)
|
||||
{
|
||||
CaptureSplitButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCaptureSplitClicked);
|
||||
}
|
||||
if (UndoSplitButton != nullptr)
|
||||
{
|
||||
UndoSplitButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleUndoSplitClicked);
|
||||
}
|
||||
if (PreviousQueueButton != nullptr)
|
||||
{
|
||||
PreviousQueueButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked);
|
||||
|
|
@ -1147,6 +1252,32 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
const bool bLiveInspectionPhase = IsLiveAttemptInspectionPhase();
|
||||
const bool bLiveSolvePhase = bHasLiveAttemptTimer
|
||||
&& LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Solving;
|
||||
const int32 MaxManualSplitCaptureCount = GetMaxManualSplitCaptureCount();
|
||||
const FHyperTwistTrainingSplitPhaseDefinition* NextSplitPhaseDefinition = FindNextSplitPhaseToCapture();
|
||||
const FString NextSplitLabel = NextSplitPhaseDefinition == nullptr
|
||||
? TEXT("none")
|
||||
: (NextSplitPhaseDefinition->Label.IsEmpty()
|
||||
? NextSplitPhaseDefinition->PhaseId
|
||||
: NextSplitPhaseDefinition->Label);
|
||||
FString CapturedSplitSummary = TEXT("none");
|
||||
if (LiveAttemptSplitCaptures.Num() > 0)
|
||||
{
|
||||
TArray<FString> CapturedSplitParts;
|
||||
CapturedSplitParts.Reserve(LiveAttemptSplitCaptures.Num());
|
||||
for (const FHyperTwistTrainingSplitCapture& SplitCapture : LiveAttemptSplitCaptures)
|
||||
{
|
||||
const FString Label = SplitCapture.Label.IsEmpty() ? SplitCapture.PhaseId : SplitCapture.Label;
|
||||
CapturedSplitParts.Add(FString::Printf(TEXT("%s@%d"), *Label, SplitCapture.TimestampMs));
|
||||
}
|
||||
CapturedSplitSummary = FString::Join(CapturedSplitParts, TEXT(" | "));
|
||||
}
|
||||
const FString FinalSplitLabel =
|
||||
TimingPolicy != nullptr
|
||||
&& TimingPolicy->SplitPhases.IsValidIndex(MaxManualSplitCaptureCount)
|
||||
? (TimingPolicy->SplitPhases[MaxManualSplitCaptureCount].Label.IsEmpty()
|
||||
? TimingPolicy->SplitPhases[MaxManualSplitCaptureCount].PhaseId
|
||||
: TimingPolicy->SplitPhases[MaxManualSplitCaptureCount].Label)
|
||||
: TEXT("solve end");
|
||||
const FString RunningSolveLabel =
|
||||
TimingPolicy != nullptr && TimingPolicy->bHideRunningTime && bLiveSolvePhase
|
||||
? TEXT("hidden")
|
||||
|
|
@ -1337,6 +1468,48 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
)));
|
||||
}
|
||||
}
|
||||
if (SplitHeaderTextBlock != nullptr)
|
||||
{
|
||||
SplitHeaderTextBlock->SetText(FText::FromString(TEXT("[Split Capture]")));
|
||||
}
|
||||
if (SplitStatusTextBlock != nullptr)
|
||||
{
|
||||
if (MaxManualSplitCaptureCount <= 0)
|
||||
{
|
||||
SplitStatusTextBlock->SetText(FText::FromString(
|
||||
TEXT("Split capture: no manual split phases are configured for this timing policy.")
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
const FString SplitStateLine = FString::Printf(
|
||||
TEXT("Manual splits %d/%d | Next: %s | Final auto-close: %s | Captured: %s"),
|
||||
LiveAttemptSplitCaptures.Num(),
|
||||
MaxManualSplitCaptureCount,
|
||||
*NextSplitLabel,
|
||||
*FinalSplitLabel,
|
||||
*CapturedSplitSummary
|
||||
);
|
||||
if (bLiveSolvePhase)
|
||||
{
|
||||
SplitStatusTextBlock->SetText(FText::FromString(
|
||||
SplitStateLine + TEXT(" | capture phase ends during solve; final phase closes on attempt stop")
|
||||
));
|
||||
}
|
||||
else if (bLiveInspectionPhase)
|
||||
{
|
||||
SplitStatusTextBlock->SetText(FText::FromString(
|
||||
SplitStateLine + TEXT(" | split capture unlocks after solve start")
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
SplitStatusTextBlock->SetText(FText::FromString(
|
||||
SplitStateLine + TEXT(" | start a live attempt to record manual split boundaries")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (AttemptTimeTextBox != nullptr)
|
||||
{
|
||||
const FString HintLabel = CurrentTrainingCase.TimeTargetMs > 0
|
||||
|
|
@ -1659,6 +1832,25 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
CancelTimerButton->SetIsEnabled(bHasLiveAttemptTimer);
|
||||
}
|
||||
if (CaptureSplitButtonLabel != nullptr)
|
||||
{
|
||||
const FString CaptureSplitLabel = NextSplitPhaseDefinition == nullptr
|
||||
? TEXT("All Splits Captured")
|
||||
: FString::Printf(TEXT("Capture %s"), *NextSplitLabel);
|
||||
CaptureSplitButtonLabel->SetText(FText::FromString(CaptureSplitLabel));
|
||||
}
|
||||
if (CaptureSplitButton != nullptr)
|
||||
{
|
||||
CaptureSplitButton->SetIsEnabled(bLiveSolvePhase && NextSplitPhaseDefinition != nullptr);
|
||||
}
|
||||
if (UndoSplitButtonLabel != nullptr)
|
||||
{
|
||||
UndoSplitButtonLabel->SetText(FText::FromString(TEXT("Undo Split")));
|
||||
}
|
||||
if (UndoSplitButton != nullptr)
|
||||
{
|
||||
UndoSplitButton->SetIsEnabled(bLiveSolvePhase && LiveAttemptSplitCaptures.Num() > 0);
|
||||
}
|
||||
if (PreviousQueueButtonLabel != nullptr)
|
||||
{
|
||||
PreviousQueueButtonLabel->SetText(FText::FromString(TEXT("Prev Queue")));
|
||||
|
|
@ -1851,3 +2043,34 @@ bool UHyperTwistCoachDashboardWidget::IsLiveAttemptInspectionPhase() const
|
|||
{
|
||||
return bHasLiveAttemptTimer && LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Inspection;
|
||||
}
|
||||
|
||||
int32 UHyperTwistCoachDashboardWidget::GetMaxManualSplitCaptureCount() const
|
||||
{
|
||||
const FHyperTwistTrainingTimingPolicy* TimingPolicy = FindActiveTimingPolicy();
|
||||
if (TimingPolicy == nullptr || TimingPolicy->SplitPhases.Num() <= 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return TimingPolicy->SplitPhases.Num() - 1;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSplitPhaseDefinition* UHyperTwistCoachDashboardWidget::FindNextSplitPhaseToCapture() const
|
||||
{
|
||||
const FHyperTwistTrainingTimingPolicy* TimingPolicy = FindActiveTimingPolicy();
|
||||
if (TimingPolicy == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int32 NextPhaseIndex = LiveAttemptSplitCaptures.Num();
|
||||
const int32 MaxManualSplitCaptureCount = GetMaxManualSplitCaptureCount();
|
||||
if (NextPhaseIndex < 0
|
||||
|| NextPhaseIndex >= MaxManualSplitCaptureCount
|
||||
|| !TimingPolicy->SplitPhases.IsValidIndex(NextPhaseIndex))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &TimingPolicy->SplitPhases[NextPhaseIndex];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ public:
|
|||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 LiveAttemptDisplayedMs = 0;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
TArray<FHyperTwistTrainingSplitCapture> LiveAttemptSplitCaptures;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
void RefreshCoachDashboardView();
|
||||
|
||||
|
|
@ -102,6 +105,12 @@ public:
|
|||
EHyperTwistTrainingAttemptResult Result
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool CaptureNextLiveAttemptSplit();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool UndoLastLiveAttemptSplit();
|
||||
|
||||
protected:
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UVerticalBox> RootLayout = nullptr;
|
||||
|
|
@ -148,6 +157,12 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TimerStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> SplitHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> SplitStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UEditableTextBox> AttemptTimeTextBox = nullptr;
|
||||
|
||||
|
|
@ -271,6 +286,18 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> CancelTimerButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> CaptureSplitButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> CaptureSplitButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> UndoSplitButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> UndoSplitButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> PreviousQueueButton = nullptr;
|
||||
|
||||
|
|
@ -331,6 +358,12 @@ protected:
|
|||
UFUNCTION()
|
||||
void HandleCancelTimerClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleCaptureSplitClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleUndoSplitClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePreviousQueueClicked();
|
||||
|
||||
|
|
@ -353,6 +386,8 @@ private:
|
|||
const FHyperTwistTrainingCoachSessionQueueStateEntry* FindSelectedQueueEntry() const;
|
||||
const FHyperTwistTrainingTimingPolicy* FindActiveTimingPolicy() const;
|
||||
bool IsLiveAttemptInspectionPhase() const;
|
||||
int32 GetMaxManualSplitCaptureCount() const;
|
||||
const FHyperTwistTrainingSplitPhaseDefinition* FindNextSplitPhaseToCapture() const;
|
||||
|
||||
EHyperTwistCoachDashboardAttemptPhase LiveAttemptPhase = EHyperTwistCoachDashboardAttemptPhase::Idle;
|
||||
double LiveAttemptStartedAtSeconds = 0.0;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue