Add live timer loop to coach dashboard
This commit is contained in:
parent
baccddc0a8
commit
1d72a8e7a5
2 changed files with 484 additions and 37 deletions
|
|
@ -9,6 +9,7 @@
|
|||
#include "Components/TextBlock.h"
|
||||
#include "Components/VerticalBox.h"
|
||||
#include "Components/VerticalBoxSlot.h"
|
||||
#include "HAL/PlatformTime.h"
|
||||
|
||||
namespace HyperTwistCoachDashboardWidgetInternal
|
||||
{
|
||||
|
|
@ -107,9 +108,37 @@ void UHyperTwistCoachDashboardWidget::NativeConstruct()
|
|||
RefreshCoachDashboardView();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
|
||||
{
|
||||
Super::NativeTick(MyGeometry, InDeltaTime);
|
||||
|
||||
if (bRefreshLiveAttemptClockOnTick && bHasLiveAttemptTimer)
|
||||
{
|
||||
RefreshLiveAttemptClock();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
|
||||
{
|
||||
RefreshTrainingState();
|
||||
if (bHasLiveAttemptTimer
|
||||
&& (!CachedRunState.Session.IsStructurallyValid()
|
||||
|| !CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid()))
|
||||
{
|
||||
bHasLiveAttemptTimer = false;
|
||||
LiveAttemptPhase = EHyperTwistCoachDashboardAttemptPhase::Idle;
|
||||
LiveAttemptStartedAtSeconds = 0.0;
|
||||
LiveAttemptSolveStartedAtSeconds = 0.0;
|
||||
LiveAttemptStoredInspectionElapsedMs = 0;
|
||||
LiveAttemptInspectionElapsedMs = 0;
|
||||
LiveAttemptSolveElapsedMs = 0;
|
||||
LiveAttemptDisplayedMs = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshLiveAttemptClock();
|
||||
}
|
||||
SyncSelectedQueueEntry();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
|
@ -264,6 +293,113 @@ FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitSyntheti
|
|||
return SubmitTrainingAttempt(Attempt, TimeMs);
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::StartLiveAttemptTimer()
|
||||
{
|
||||
if (!CachedRunState.Session.IsStructurallyValid() || !CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bHasLiveAttemptTimer)
|
||||
{
|
||||
return AdvanceLiveAttemptToSolvePhase();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingTimingPolicy* TimingPolicy = FindActiveTimingPolicy();
|
||||
const bool bUseInspection = TimingPolicy != nullptr
|
||||
&& TimingPolicy->bInspectionEnabled
|
||||
&& TimingPolicy->InspectionDurationMs > 0;
|
||||
|
||||
bHasLiveAttemptTimer = true;
|
||||
LiveAttemptPhase = bUseInspection
|
||||
? EHyperTwistCoachDashboardAttemptPhase::Inspection
|
||||
: EHyperTwistCoachDashboardAttemptPhase::Solving;
|
||||
LiveAttemptStartedAtSeconds = FPlatformTime::Seconds();
|
||||
LiveAttemptSolveStartedAtSeconds = bUseInspection ? 0.0 : LiveAttemptStartedAtSeconds;
|
||||
LiveAttemptStoredInspectionElapsedMs = 0;
|
||||
LiveAttemptInspectionElapsedMs = 0;
|
||||
LiveAttemptSolveElapsedMs = 0;
|
||||
LiveAttemptDisplayedMs = 0;
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::AdvanceLiveAttemptToSolvePhase()
|
||||
{
|
||||
if (!bHasLiveAttemptTimer || LiveAttemptPhase != EHyperTwistCoachDashboardAttemptPhase::Inspection)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RefreshLiveAttemptClock();
|
||||
LiveAttemptStoredInspectionElapsedMs = LiveAttemptInspectionElapsedMs;
|
||||
LiveAttemptSolveStartedAtSeconds = FPlatformTime::Seconds();
|
||||
LiveAttemptPhase = EHyperTwistCoachDashboardAttemptPhase::Solving;
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::CancelLiveAttemptTimer()
|
||||
{
|
||||
bHasLiveAttemptTimer = false;
|
||||
LiveAttemptPhase = EHyperTwistCoachDashboardAttemptPhase::Idle;
|
||||
LiveAttemptStartedAtSeconds = 0.0;
|
||||
LiveAttemptSolveStartedAtSeconds = 0.0;
|
||||
LiveAttemptStoredInspectionElapsedMs = 0;
|
||||
LiveAttemptInspectionElapsedMs = 0;
|
||||
LiveAttemptSolveElapsedMs = 0;
|
||||
LiveAttemptDisplayedMs = 0;
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitLiveTimedAttempt(
|
||||
const EHyperTwistTrainingAttemptResult Result
|
||||
)
|
||||
{
|
||||
if (!bHasLiveAttemptTimer
|
||||
|| LiveAttemptPhase != EHyperTwistCoachDashboardAttemptPhase::Solving
|
||||
|| !CachedRunState.Session.IsStructurallyValid()
|
||||
|| !CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingRunStepResult();
|
||||
}
|
||||
|
||||
RefreshLiveAttemptClock();
|
||||
|
||||
FHyperTwistTrainingAttempt Attempt;
|
||||
Attempt.AttemptId = FString::Printf(
|
||||
TEXT("attempt_%s_%s"),
|
||||
*CachedRunState.Session.TrainingSessionId,
|
||||
*FGuid::NewGuid().ToString(EGuidFormats::Digits)
|
||||
);
|
||||
Attempt.TrainingSessionId = CachedRunState.Session.TrainingSessionId;
|
||||
Attempt.CaseId = CachedRunState.CurrentSelection.TrainingCase.CaseId;
|
||||
Attempt.Result = Result;
|
||||
Attempt.TotalTimeMs = FMath::Max(LiveAttemptSolveElapsedMs, 0);
|
||||
Attempt.ExecutionTimeMs = FMath::Max(LiveAttemptSolveElapsedMs, 0);
|
||||
Attempt.TimingBreakdown.InspectionElapsedMs = FMath::Max(LiveAttemptInspectionElapsedMs, 0);
|
||||
Attempt.TimingBreakdown.RawSolveTimeMs = FMath::Max(LiveAttemptSolveElapsedMs, 0);
|
||||
Attempt.TimingBreakdown.FinalTimeMs = FMath::Max(LiveAttemptSolveElapsedMs, 0);
|
||||
Attempt.TimingBreakdown.bDidNotFinish =
|
||||
Result == EHyperTwistTrainingAttemptResult::Timeout
|
||||
|| Result == EHyperTwistTrainingAttemptResult::DNF
|
||||
|| Result == EHyperTwistTrainingAttemptResult::Aborted;
|
||||
if (Result == EHyperTwistTrainingAttemptResult::DNF)
|
||||
{
|
||||
Attempt.TimingBreakdown.ManualPenalty = EHyperTwistTrainingPenalty::DNF;
|
||||
Attempt.TimingBreakdown.AppliedPenalty = EHyperTwistTrainingPenalty::DNF;
|
||||
}
|
||||
Attempt.ReplayId = CachedRunState.ReplayPacket.ReplayId;
|
||||
Attempt.CompletedAtUtc = FDateTime::UtcNow().ToIso8601();
|
||||
|
||||
const FHyperTwistTrainingRunStepResult StepResult = SubmitTrainingAttempt(
|
||||
Attempt,
|
||||
FMath::Max(LiveAttemptSolveElapsedMs, 0)
|
||||
);
|
||||
CancelLiveAttemptTimer();
|
||||
return StepResult;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePrimaryActionClicked()
|
||||
{
|
||||
ExecutePrimaryCoachAction();
|
||||
|
|
@ -315,40 +451,68 @@ void UHyperTwistCoachDashboardWidget::HandleClearRunClicked()
|
|||
|
||||
void UHyperTwistCoachDashboardWidget::HandleRecordSuccessClicked()
|
||||
{
|
||||
SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult::Success,
|
||||
ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::Success)
|
||||
);
|
||||
if (bHasLiveAttemptTimer)
|
||||
{
|
||||
SubmitLiveTimedAttempt(EHyperTwistTrainingAttemptResult::Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult::Success,
|
||||
ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::Success)
|
||||
);
|
||||
}
|
||||
SyncSelectedQueueEntry();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleRecordFailureClicked()
|
||||
{
|
||||
SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult::Failure,
|
||||
ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::Failure)
|
||||
);
|
||||
if (bHasLiveAttemptTimer)
|
||||
{
|
||||
SubmitLiveTimedAttempt(EHyperTwistTrainingAttemptResult::Failure);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult::Failure,
|
||||
ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::Failure)
|
||||
);
|
||||
}
|
||||
SyncSelectedQueueEntry();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleRecordTimeoutClicked()
|
||||
{
|
||||
SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult::Timeout,
|
||||
ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::Timeout)
|
||||
);
|
||||
if (bHasLiveAttemptTimer)
|
||||
{
|
||||
SubmitLiveTimedAttempt(EHyperTwistTrainingAttemptResult::Timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult::Timeout,
|
||||
ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::Timeout)
|
||||
);
|
||||
}
|
||||
SyncSelectedQueueEntry();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleRecordDnfClicked()
|
||||
{
|
||||
SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult::DNF,
|
||||
ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::DNF)
|
||||
);
|
||||
if (bHasLiveAttemptTimer)
|
||||
{
|
||||
SubmitLiveTimedAttempt(EHyperTwistTrainingAttemptResult::DNF);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitSyntheticCurrentAttempt(
|
||||
EHyperTwistTrainingAttemptResult::DNF,
|
||||
ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::DNF)
|
||||
);
|
||||
}
|
||||
SyncSelectedQueueEntry();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
|
@ -372,6 +536,23 @@ void UHyperTwistCoachDashboardWidget::HandleUseAverageTimeClicked()
|
|||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleStartTimerClicked()
|
||||
{
|
||||
if (bHasLiveAttemptTimer && IsLiveAttemptInspectionPhase())
|
||||
{
|
||||
AdvanceLiveAttemptToSolvePhase();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartLiveAttemptTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleCancelTimerClicked()
|
||||
{
|
||||
CancelLiveAttemptTimer();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked()
|
||||
{
|
||||
SelectPreviousQueueEntry();
|
||||
|
|
@ -472,6 +653,18 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachAttemptInputStatus"),
|
||||
2
|
||||
);
|
||||
TimerHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTimerHeader"),
|
||||
2
|
||||
);
|
||||
TimerStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTimerStatus"),
|
||||
8
|
||||
);
|
||||
FollowUpTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
|
|
@ -647,6 +840,40 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
UseAverageTimeButtonLabel = UseAverageTimeLabelRaw;
|
||||
}
|
||||
|
||||
UHorizontalBox* LiveTimerRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardLiveTimerRow")
|
||||
);
|
||||
if (LiveTimerRow != nullptr)
|
||||
{
|
||||
if (UVerticalBoxSlot* LiveTimerRowSlot = RootLayout->AddChildToVerticalBox(LiveTimerRow))
|
||||
{
|
||||
LiveTimerRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
UTextBlock* StartTimerLabelRaw = nullptr;
|
||||
StartTimerButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
LiveTimerRow,
|
||||
TEXT("StartTimerButton"),
|
||||
TEXT("StartTimerButtonLabel"),
|
||||
TEXT("Start Attempt"),
|
||||
StartTimerLabelRaw
|
||||
);
|
||||
StartTimerButtonLabel = StartTimerLabelRaw;
|
||||
|
||||
UTextBlock* CancelTimerLabelRaw = nullptr;
|
||||
CancelTimerButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
LiveTimerRow,
|
||||
TEXT("CancelTimerButton"),
|
||||
TEXT("CancelTimerButtonLabel"),
|
||||
TEXT("Cancel Timer"),
|
||||
CancelTimerLabelRaw
|
||||
);
|
||||
CancelTimerButtonLabel = CancelTimerLabelRaw;
|
||||
}
|
||||
|
||||
UHorizontalBox* RunControlRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardRunControlButtons")
|
||||
|
|
@ -818,6 +1045,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
{
|
||||
UseAverageTimeButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleUseAverageTimeClicked);
|
||||
}
|
||||
if (StartTimerButton != nullptr)
|
||||
{
|
||||
StartTimerButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartTimerClicked);
|
||||
}
|
||||
if (CancelTimerButton != nullptr)
|
||||
{
|
||||
CancelTimerButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCancelTimerClicked);
|
||||
}
|
||||
if (PreviousQueueButton != nullptr)
|
||||
{
|
||||
PreviousQueueButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked);
|
||||
|
|
@ -904,6 +1139,29 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
const int32 ResolvedFailureTimeMs = ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::Failure);
|
||||
const int32 ResolvedTimeoutTimeMs = ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::Timeout);
|
||||
const int32 ResolvedDnfTimeMs = ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult::DNF);
|
||||
const FHyperTwistTrainingTimingPolicy* TimingPolicy = FindActiveTimingPolicy();
|
||||
const bool bHasInspectionWindow = TimingPolicy != nullptr
|
||||
&& TimingPolicy->bInspectionEnabled
|
||||
&& TimingPolicy->InspectionDurationMs > 0;
|
||||
const int32 InspectionLimitMs = bHasInspectionWindow ? FMath::Max(TimingPolicy->InspectionDurationMs, 0) : 0;
|
||||
const bool bLiveInspectionPhase = IsLiveAttemptInspectionPhase();
|
||||
const bool bLiveSolvePhase = bHasLiveAttemptTimer
|
||||
&& LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Solving;
|
||||
const FString RunningSolveLabel =
|
||||
TimingPolicy != nullptr && TimingPolicy->bHideRunningTime && bLiveSolvePhase
|
||||
? TEXT("hidden")
|
||||
: FString::FromInt(LiveAttemptSolveElapsedMs);
|
||||
const EHyperTwistTrainingPenalty LiveInspectionPenalty =
|
||||
(bHasInspectionWindow
|
||||
&& TimingPolicy->bInspectionPenaltiesEnabled
|
||||
&& LiveAttemptInspectionElapsedMs > InspectionLimitMs
|
||||
&& LiveAttemptInspectionElapsedMs <= InspectionLimitMs + 2000)
|
||||
? EHyperTwistTrainingPenalty::Plus2
|
||||
: ((bHasInspectionWindow
|
||||
&& TimingPolicy->bInspectionPenaltiesEnabled
|
||||
&& LiveAttemptInspectionElapsedMs > InspectionLimitMs + 2000)
|
||||
? EHyperTwistTrainingPenalty::DNF
|
||||
: EHyperTwistTrainingPenalty::None);
|
||||
const FString VerificationNotePreview = CachedRepositoryRoundTripVerification.VerificationNotes.Num() > 0
|
||||
? CachedRepositoryRoundTripVerification.VerificationNotes[0]
|
||||
: TEXT("none");
|
||||
|
|
@ -1042,13 +1300,50 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
ResolvedDnfTimeMs
|
||||
)));
|
||||
}
|
||||
if (TimerHeaderTextBlock != nullptr)
|
||||
{
|
||||
TimerHeaderTextBlock->SetText(FText::FromString(TEXT("[Live Timer]")));
|
||||
}
|
||||
if (TimerStatusTextBlock != nullptr)
|
||||
{
|
||||
if (bLiveInspectionPhase)
|
||||
{
|
||||
const int32 RemainingInspectionMs = bHasInspectionWindow
|
||||
? FMath::Max(InspectionLimitMs - LiveAttemptInspectionElapsedMs, 0)
|
||||
: 0;
|
||||
TimerStatusTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Phase: inspection | elapsed %d ms | remaining %d ms | pending penalty: %s | click Start Solve when ready"),
|
||||
LiveAttemptInspectionElapsedMs,
|
||||
RemainingInspectionMs,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(LiveInspectionPenalty)
|
||||
)));
|
||||
}
|
||||
else if (bLiveSolvePhase)
|
||||
{
|
||||
TimerStatusTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Phase: solving | inspection %d ms | solve %s ms | finish with Success / Failure / Timeout / DNF"),
|
||||
LiveAttemptInspectionElapsedMs,
|
||||
*RunningSolveLabel
|
||||
)));
|
||||
}
|
||||
else
|
||||
{
|
||||
const FString InspectionLabel = bHasInspectionWindow
|
||||
? FString::Printf(TEXT("inspection target %d ms"), InspectionLimitMs)
|
||||
: TEXT("no inspection window");
|
||||
TimerStatusTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Timer idle | %s | start a live attempt for less-synthetic run entry"),
|
||||
*InspectionLabel
|
||||
)));
|
||||
}
|
||||
}
|
||||
if (AttemptTimeTextBox != nullptr)
|
||||
{
|
||||
const FString HintLabel = CurrentTrainingCase.TimeTargetMs > 0
|
||||
? FString::Printf(TEXT("Enter attempt ms (target %d)"), CurrentTrainingCase.TimeTargetMs)
|
||||
: TEXT("Enter attempt ms");
|
||||
AttemptTimeTextBox->SetHintText(FText::FromString(HintLabel));
|
||||
AttemptTimeTextBox->SetIsEnabled(bHasRunnableCurrentCase);
|
||||
AttemptTimeTextBox->SetIsEnabled(!bHasLiveAttemptTimer && bHasRunnableCurrentCase);
|
||||
}
|
||||
if (FollowUpTextBlock != nullptr)
|
||||
{
|
||||
|
|
@ -1237,9 +1532,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
if (PrimaryActionButton != nullptr)
|
||||
{
|
||||
PrimaryActionButton->SetIsEnabled(
|
||||
CachedCoachPanelState.bCanStartQueuedRun
|
||||
|| CachedCoachPanelState.bCanStartCoachFollowUpRun
|
||||
|| CachedCoachPanelState.bCanStartCoachRecommendedRun
|
||||
!bHasLiveAttemptTimer
|
||||
&& (
|
||||
CachedCoachPanelState.bCanStartQueuedRun
|
||||
|| CachedCoachPanelState.bCanStartCoachFollowUpRun
|
||||
|| CachedCoachPanelState.bCanStartCoachRecommendedRun
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1253,7 +1551,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (DeferButton != nullptr)
|
||||
{
|
||||
DeferButton->SetIsEnabled(FindSelectedQueueEntry() != nullptr);
|
||||
DeferButton->SetIsEnabled(!bHasLiveAttemptTimer && FindSelectedQueueEntry() != nullptr);
|
||||
}
|
||||
if (PromoteButtonLabel != nullptr)
|
||||
{
|
||||
|
|
@ -1261,7 +1559,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (PromoteButton != nullptr)
|
||||
{
|
||||
PromoteButton->SetIsEnabled(!FindFirstDeferredQueueEntryId().IsEmpty());
|
||||
PromoteButton->SetIsEnabled(!bHasLiveAttemptTimer && !FindFirstDeferredQueueEntryId().IsEmpty());
|
||||
}
|
||||
if (CompleteRunButtonLabel != nullptr)
|
||||
{
|
||||
|
|
@ -1269,7 +1567,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (CompleteRunButton != nullptr)
|
||||
{
|
||||
CompleteRunButton->SetIsEnabled(bHasActiveRun);
|
||||
CompleteRunButton->SetIsEnabled(bHasActiveRun && !bHasLiveAttemptTimer);
|
||||
}
|
||||
if (ClearRunButtonLabel != nullptr)
|
||||
{
|
||||
|
|
@ -1277,39 +1575,47 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (ClearRunButton != nullptr)
|
||||
{
|
||||
ClearRunButton->SetIsEnabled(bHasActiveRun);
|
||||
ClearRunButton->SetIsEnabled(bHasActiveRun && !bHasLiveAttemptTimer);
|
||||
}
|
||||
if (RecordSuccessButtonLabel != nullptr)
|
||||
{
|
||||
RecordSuccessButtonLabel->SetText(FText::FromString(TEXT("Record Success")));
|
||||
RecordSuccessButtonLabel->SetText(FText::FromString(
|
||||
bLiveSolvePhase ? TEXT("Stop Success") : TEXT("Record Success")
|
||||
));
|
||||
}
|
||||
if (RecordSuccessButton != nullptr)
|
||||
{
|
||||
RecordSuccessButton->SetIsEnabled(bHasRunnableCurrentCase);
|
||||
RecordSuccessButton->SetIsEnabled(bLiveSolvePhase || (!bHasLiveAttemptTimer && bHasRunnableCurrentCase));
|
||||
}
|
||||
if (RecordFailureButtonLabel != nullptr)
|
||||
{
|
||||
RecordFailureButtonLabel->SetText(FText::FromString(TEXT("Record Failure")));
|
||||
RecordFailureButtonLabel->SetText(FText::FromString(
|
||||
bLiveSolvePhase ? TEXT("Stop Failure") : TEXT("Record Failure")
|
||||
));
|
||||
}
|
||||
if (RecordFailureButton != nullptr)
|
||||
{
|
||||
RecordFailureButton->SetIsEnabled(bHasRunnableCurrentCase);
|
||||
RecordFailureButton->SetIsEnabled(bLiveSolvePhase || (!bHasLiveAttemptTimer && bHasRunnableCurrentCase));
|
||||
}
|
||||
if (RecordTimeoutButtonLabel != nullptr)
|
||||
{
|
||||
RecordTimeoutButtonLabel->SetText(FText::FromString(TEXT("Record Timeout")));
|
||||
RecordTimeoutButtonLabel->SetText(FText::FromString(
|
||||
bLiveSolvePhase ? TEXT("Stop Timeout") : TEXT("Record Timeout")
|
||||
));
|
||||
}
|
||||
if (RecordTimeoutButton != nullptr)
|
||||
{
|
||||
RecordTimeoutButton->SetIsEnabled(bHasRunnableCurrentCase);
|
||||
RecordTimeoutButton->SetIsEnabled(bLiveSolvePhase || (!bHasLiveAttemptTimer && bHasRunnableCurrentCase));
|
||||
}
|
||||
if (RecordDnfButtonLabel != nullptr)
|
||||
{
|
||||
RecordDnfButtonLabel->SetText(FText::FromString(TEXT("Record DNF")));
|
||||
RecordDnfButtonLabel->SetText(FText::FromString(
|
||||
bLiveSolvePhase ? TEXT("Stop DNF") : TEXT("Record DNF")
|
||||
));
|
||||
}
|
||||
if (RecordDnfButton != nullptr)
|
||||
{
|
||||
RecordDnfButton->SetIsEnabled(bHasRunnableCurrentCase);
|
||||
RecordDnfButton->SetIsEnabled(bLiveSolvePhase || (!bHasLiveAttemptTimer && bHasRunnableCurrentCase));
|
||||
}
|
||||
if (UseTargetTimeButtonLabel != nullptr)
|
||||
{
|
||||
|
|
@ -1317,7 +1623,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (UseTargetTimeButton != nullptr)
|
||||
{
|
||||
UseTargetTimeButton->SetIsEnabled(bHasRunnableCurrentCase && CurrentTrainingCase.TimeTargetMs > 0);
|
||||
UseTargetTimeButton->SetIsEnabled(
|
||||
!bHasLiveAttemptTimer && bHasRunnableCurrentCase && CurrentTrainingCase.TimeTargetMs > 0
|
||||
);
|
||||
}
|
||||
if (UseAverageTimeButtonLabel != nullptr)
|
||||
{
|
||||
|
|
@ -1325,7 +1633,31 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (UseAverageTimeButton != nullptr)
|
||||
{
|
||||
UseAverageTimeButton->SetIsEnabled(bHasActiveRun && CachedRunSummary.AverageTotalTimeMs > 0);
|
||||
UseAverageTimeButton->SetIsEnabled(
|
||||
!bHasLiveAttemptTimer && bHasActiveRun && CachedRunSummary.AverageTotalTimeMs > 0
|
||||
);
|
||||
}
|
||||
if (StartTimerButtonLabel != nullptr)
|
||||
{
|
||||
StartTimerButtonLabel->SetText(FText::FromString(
|
||||
bLiveInspectionPhase ? TEXT("Start Solve")
|
||||
: (bHasLiveAttemptTimer ? TEXT("Timer Active") : TEXT("Start Attempt"))
|
||||
));
|
||||
}
|
||||
if (StartTimerButton != nullptr)
|
||||
{
|
||||
StartTimerButton->SetIsEnabled(
|
||||
bHasRunnableCurrentCase
|
||||
&& (!bHasLiveAttemptTimer || bLiveInspectionPhase)
|
||||
);
|
||||
}
|
||||
if (CancelTimerButtonLabel != nullptr)
|
||||
{
|
||||
CancelTimerButtonLabel->SetText(FText::FromString(TEXT("Cancel Timer")));
|
||||
}
|
||||
if (CancelTimerButton != nullptr)
|
||||
{
|
||||
CancelTimerButton->SetIsEnabled(bHasLiveAttemptTimer);
|
||||
}
|
||||
if (PreviousQueueButtonLabel != nullptr)
|
||||
{
|
||||
|
|
@ -1333,7 +1665,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (PreviousQueueButton != nullptr)
|
||||
{
|
||||
PreviousQueueButton->SetIsEnabled(CachedCoachSessionQueueState.Entries.Num() > 1);
|
||||
PreviousQueueButton->SetIsEnabled(!bHasLiveAttemptTimer && CachedCoachSessionQueueState.Entries.Num() > 1);
|
||||
}
|
||||
if (StartSelectedButtonLabel != nullptr)
|
||||
{
|
||||
|
|
@ -1346,7 +1678,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
&& (SelectedEntry->EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Ready
|
||||
|| (SelectedEntry->EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Pending
|
||||
&& SelectedEntry->bCanStartImmediately));
|
||||
StartSelectedButton->SetIsEnabled(bSelectedRunnable);
|
||||
StartSelectedButton->SetIsEnabled(!bHasLiveAttemptTimer && bSelectedRunnable);
|
||||
}
|
||||
if (NextQueueButtonLabel != nullptr)
|
||||
{
|
||||
|
|
@ -1354,7 +1686,38 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
}
|
||||
if (NextQueueButton != nullptr)
|
||||
{
|
||||
NextQueueButton->SetIsEnabled(CachedCoachSessionQueueState.Entries.Num() > 1);
|
||||
NextQueueButton->SetIsEnabled(!bHasLiveAttemptTimer && CachedCoachSessionQueueState.Entries.Num() > 1);
|
||||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::RefreshLiveAttemptClock()
|
||||
{
|
||||
if (!bHasLiveAttemptTimer)
|
||||
{
|
||||
LiveAttemptInspectionElapsedMs = 0;
|
||||
LiveAttemptSolveElapsedMs = 0;
|
||||
LiveAttemptDisplayedMs = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const double NowSeconds = FPlatformTime::Seconds();
|
||||
if (LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Inspection)
|
||||
{
|
||||
LiveAttemptInspectionElapsedMs = FMath::Max(
|
||||
0,
|
||||
static_cast<int32>((NowSeconds - LiveAttemptStartedAtSeconds) * 1000.0)
|
||||
);
|
||||
LiveAttemptSolveElapsedMs = 0;
|
||||
LiveAttemptDisplayedMs = LiveAttemptInspectionElapsedMs;
|
||||
}
|
||||
else if (LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Solving)
|
||||
{
|
||||
LiveAttemptInspectionElapsedMs = FMath::Max(LiveAttemptStoredInspectionElapsedMs, 0);
|
||||
LiveAttemptSolveElapsedMs = FMath::Max(
|
||||
0,
|
||||
static_cast<int32>((NowSeconds - LiveAttemptSolveStartedAtSeconds) * 1000.0)
|
||||
);
|
||||
LiveAttemptDisplayedMs = LiveAttemptSolveElapsedMs;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1473,3 +1836,18 @@ const FHyperTwistTrainingCoachSessionQueueStateEntry* UHyperTwistCoachDashboardW
|
|||
|
||||
return &CachedCoachSessionQueueState.Entries[SelectedQueueEntryIndex];
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingTimingPolicy* UHyperTwistCoachDashboardWidget::FindActiveTimingPolicy() const
|
||||
{
|
||||
if (!CachedRunState.Session.IsStructurallyValid() || !CachedRunState.ActiveDeck.IsStructurallyValid())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &CachedRunState.ActiveDeck.TimingPolicy;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::IsLiveAttemptInspectionPhase() const
|
||||
{
|
||||
return bHasLiveAttemptTimer && LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Inspection;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ class UEditableTextBox;
|
|||
class UTextBlock;
|
||||
class UVerticalBox;
|
||||
|
||||
enum class EHyperTwistCoachDashboardAttemptPhase : uint8
|
||||
{
|
||||
Idle,
|
||||
Inspection,
|
||||
Solving
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget
|
||||
{
|
||||
|
|
@ -16,6 +23,7 @@ class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistT
|
|||
|
||||
public:
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 DefaultDeferredHours = 24;
|
||||
|
|
@ -35,9 +43,24 @@ public:
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool bPreferActiveQueueSelectionOnRefresh = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool bRefreshLiveAttemptClockOnTick = true;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 SelectedQueueEntryIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool bHasLiveAttemptTimer = false;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 LiveAttemptInspectionElapsedMs = 0;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 LiveAttemptSolveElapsedMs = 0;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 LiveAttemptDisplayedMs = 0;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
void RefreshCoachDashboardView();
|
||||
|
||||
|
|
@ -65,6 +88,20 @@ public:
|
|||
int32 TimeMs
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool StartLiveAttemptTimer();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool AdvanceLiveAttemptToSolvePhase();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
void CancelLiveAttemptTimer();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
FHyperTwistTrainingRunStepResult SubmitLiveTimedAttempt(
|
||||
EHyperTwistTrainingAttemptResult Result
|
||||
);
|
||||
|
||||
protected:
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UVerticalBox> RootLayout = nullptr;
|
||||
|
|
@ -105,6 +142,12 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> AttemptInputStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TimerHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TimerStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UEditableTextBox> AttemptTimeTextBox = nullptr;
|
||||
|
||||
|
|
@ -216,6 +259,18 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> UseAverageTimeButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> StartTimerButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> StartTimerButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> CancelTimerButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> CancelTimerButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> PreviousQueueButton = nullptr;
|
||||
|
||||
|
|
@ -270,6 +325,12 @@ protected:
|
|||
UFUNCTION()
|
||||
void HandleUseAverageTimeClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleStartTimerClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleCancelTimerClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePreviousQueueClicked();
|
||||
|
||||
|
|
@ -283,10 +344,18 @@ private:
|
|||
void EnsureDefaultDashboardBuilt();
|
||||
void SyncSelectedQueueEntry();
|
||||
void UpdateDashboardPresentation();
|
||||
void RefreshLiveAttemptClock();
|
||||
FString BuildDefaultDeferredUntilUtc() const;
|
||||
FString FindFirstDeferredQueueEntryId() const;
|
||||
bool TryParseAttemptTimeOverride(int32& OutTimeMs) const;
|
||||
int32 ResolveAttemptTimeMs(EHyperTwistTrainingAttemptResult Result) const;
|
||||
void SetAttemptTimeText(int32 TimeMs);
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry* FindSelectedQueueEntry() const;
|
||||
const FHyperTwistTrainingTimingPolicy* FindActiveTimingPolicy() const;
|
||||
bool IsLiveAttemptInspectionPhase() const;
|
||||
|
||||
EHyperTwistCoachDashboardAttemptPhase LiveAttemptPhase = EHyperTwistCoachDashboardAttemptPhase::Idle;
|
||||
double LiveAttemptStartedAtSeconds = 0.0;
|
||||
double LiveAttemptSolveStartedAtSeconds = 0.0;
|
||||
int32 LiveAttemptStoredInspectionElapsedMs = 0;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue