Refine coach dashboard operations surface
This commit is contained in:
parent
23fa842616
commit
9c46be27b9
2 changed files with 289 additions and 2 deletions
|
|
@ -38,6 +38,7 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
{
|
||||
Slot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, static_cast<float>(PaddingBottom)));
|
||||
}
|
||||
TextBlock->SetAutoWrapText(true);
|
||||
return TextBlock;
|
||||
}
|
||||
|
||||
|
|
@ -237,6 +238,20 @@ void UHyperTwistCoachDashboardWidget::HandlePromoteClicked()
|
|||
PromoteFirstDeferredQueueEntry();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked()
|
||||
{
|
||||
CompleteActiveTrainingRun(false);
|
||||
SyncSelectedQueueEntry();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleClearRunClicked()
|
||||
{
|
||||
ClearActiveTrainingRun();
|
||||
SyncSelectedQueueEntry();
|
||||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked()
|
||||
{
|
||||
SelectPreviousQueueEntry();
|
||||
|
|
@ -285,6 +300,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
TEXT("CoachFocus"),
|
||||
8
|
||||
);
|
||||
ActiveRunHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachActiveRunHeader"),
|
||||
2
|
||||
);
|
||||
ActiveRunStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachActiveRunStatus"),
|
||||
2
|
||||
);
|
||||
ActiveRunSummaryTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachActiveRunSummary"),
|
||||
8
|
||||
);
|
||||
FollowUpTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
|
|
@ -319,12 +352,42 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachQueueExecution"),
|
||||
8
|
||||
);
|
||||
ScheduleHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachScheduleHeader"),
|
||||
2
|
||||
);
|
||||
ScheduleStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachScheduleStatus"),
|
||||
2
|
||||
);
|
||||
SchedulePolicyTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachSchedulePolicy"),
|
||||
8
|
||||
);
|
||||
VerificationStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachVerificationStatus"),
|
||||
2
|
||||
);
|
||||
VerificationHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachVerificationHeader"),
|
||||
2
|
||||
);
|
||||
VerificationDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachVerificationDetail"),
|
||||
10
|
||||
);
|
||||
|
||||
|
|
@ -384,6 +447,40 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
PromoteButtonLabel = PromoteLabelRaw;
|
||||
}
|
||||
|
||||
UHorizontalBox* RunControlRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardRunControlButtons")
|
||||
);
|
||||
if (RunControlRow != nullptr)
|
||||
{
|
||||
if (UVerticalBoxSlot* RunControlRowSlot = RootLayout->AddChildToVerticalBox(RunControlRow))
|
||||
{
|
||||
RunControlRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
UTextBlock* CompleteRunLabelRaw = nullptr;
|
||||
CompleteRunButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
RunControlRow,
|
||||
TEXT("CompleteRunButton"),
|
||||
TEXT("CompleteRunButtonLabel"),
|
||||
TEXT("Complete Run"),
|
||||
CompleteRunLabelRaw
|
||||
);
|
||||
CompleteRunButtonLabel = CompleteRunLabelRaw;
|
||||
|
||||
UTextBlock* ClearRunLabelRaw = nullptr;
|
||||
ClearRunButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
RunControlRow,
|
||||
TEXT("ClearRunButton"),
|
||||
TEXT("ClearRunButtonLabel"),
|
||||
TEXT("Clear Run"),
|
||||
ClearRunLabelRaw
|
||||
);
|
||||
ClearRunButtonLabel = ClearRunLabelRaw;
|
||||
}
|
||||
|
||||
UHorizontalBox* QueueNavRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardQueueNavButtons")
|
||||
|
|
@ -445,6 +542,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
{
|
||||
PromoteButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePromoteClicked);
|
||||
}
|
||||
if (CompleteRunButton != nullptr)
|
||||
{
|
||||
CompleteRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked);
|
||||
}
|
||||
if (ClearRunButton != nullptr)
|
||||
{
|
||||
ClearRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleClearRunClicked);
|
||||
}
|
||||
if (PreviousQueueButton != nullptr)
|
||||
{
|
||||
PreviousQueueButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked);
|
||||
|
|
@ -515,6 +620,18 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedQueueEntry()
|
|||
|
||||
void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
||||
{
|
||||
const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid()
|
||||
&& CachedRunState.ActiveDeck.IsStructurallyValid();
|
||||
const FString ActiveCaseId = CachedRunState.CurrentSelection.TrainingCase.CaseId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: CachedRunState.CurrentSelection.TrainingCase.CaseId;
|
||||
const FString ActiveDeckLabel = CachedRunState.ActiveDeck.Title.IsEmpty()
|
||||
? (CachedRunState.ActiveDeck.DeckId.IsEmpty() ? TEXT("n/a") : CachedRunState.ActiveDeck.DeckId)
|
||||
: CachedRunState.ActiveDeck.Title;
|
||||
const FString VerificationNotePreview = CachedRepositoryRoundTripVerification.VerificationNotes.Num() > 0
|
||||
? CachedRepositoryRoundTripVerification.VerificationNotes[0]
|
||||
: TEXT("none");
|
||||
|
||||
if (HeadlineTextBlock != nullptr)
|
||||
{
|
||||
HeadlineTextBlock->SetText(FText::FromString(CachedCoachPanelState.Headline));
|
||||
|
|
@ -532,6 +649,40 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
CachedCoachPanelState.FocusCaseCount
|
||||
)));
|
||||
}
|
||||
if (ActiveRunHeaderTextBlock != nullptr)
|
||||
{
|
||||
ActiveRunHeaderTextBlock->SetText(FText::FromString(TEXT("[Active Run]")));
|
||||
}
|
||||
if (ActiveRunStatusTextBlock != nullptr)
|
||||
{
|
||||
const FString ActiveRunLine = bHasActiveRun
|
||||
? FString::Printf(
|
||||
TEXT("Session: %s | State: %s | Deck: %s | Mode: %s | Current case: %s | Remaining: %d"),
|
||||
*CachedRunState.Session.TrainingSessionId,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(CachedRunState.Session.SessionState),
|
||||
*ActiveDeckLabel,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(CachedRunState.Session.Mode),
|
||||
*ActiveCaseId,
|
||||
CachedRunState.RemainingCaseIds.Num()
|
||||
)
|
||||
: TEXT("No active run. Queue and coach guidance are ready for launch from this screen.");
|
||||
ActiveRunStatusTextBlock->SetText(FText::FromString(ActiveRunLine));
|
||||
}
|
||||
if (ActiveRunSummaryTextBlock != nullptr)
|
||||
{
|
||||
ActiveRunSummaryTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Attempts: %d total | %d completed | Success %d | Failure %d | Timeout %d | DNF %d | Best %d ms | Avg %d ms | Last case: %s"),
|
||||
CachedRunSummary.AttemptCount,
|
||||
CachedRunSummary.CompletedAttemptCount,
|
||||
CachedRunSummary.SuccessCount,
|
||||
CachedRunSummary.FailureCount,
|
||||
CachedRunSummary.TimeoutCount,
|
||||
CachedRunSummary.DNFCount,
|
||||
CachedRunSummary.BestTotalTimeMs,
|
||||
CachedRunSummary.AverageTotalTimeMs,
|
||||
CachedRunSummary.LastCaseId.IsEmpty() ? TEXT("n/a") : *CachedRunSummary.LastCaseId
|
||||
)));
|
||||
}
|
||||
if (FollowUpTextBlock != nullptr)
|
||||
{
|
||||
const FString FollowUpLine =
|
||||
|
|
@ -614,20 +765,98 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
if (QueueExecutionTextBlock != nullptr)
|
||||
{
|
||||
QueueExecutionTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Execution: queued %d | started %d | completed %d | deferred %d | manual reschedules %d | outstanding deferred: %s | completion gap: %s"),
|
||||
TEXT("Execution: queued %d | started %d | completed %d | deferred %d | promoted %d | manual reschedules %d | outstanding deferred: %s | completion gap: %s | archive-aware friction: %s | blocked follow-up friction: %s"),
|
||||
CachedCoachQueueExecutionSummary.QueuedEventCount,
|
||||
CachedCoachQueueExecutionSummary.StartedEventCount,
|
||||
CachedCoachQueueExecutionSummary.CompletedEventCount,
|
||||
CachedCoachQueueExecutionSummary.DeferredEventCount,
|
||||
CachedCoachQueueExecutionSummary.PromotedEventCount,
|
||||
CachedCoachQueueExecutionSummary.ManualRescheduleEventCount,
|
||||
CachedCoachQueueExecutionSummary.bHasOutstandingDeferredWork ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachQueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no")
|
||||
CachedCoachQueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachQueueExecutionSummary.bHasArchiveAwareFriction ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachQueueExecutionSummary.bHasBlockedFollowUpFriction ? TEXT("yes") : TEXT("no")
|
||||
)));
|
||||
}
|
||||
if (ScheduleHeaderTextBlock != nullptr)
|
||||
{
|
||||
ScheduleHeaderTextBlock->SetText(FText::FromString(TEXT("[Schedule / Friction]")));
|
||||
}
|
||||
if (ScheduleStatusTextBlock != nullptr)
|
||||
{
|
||||
ScheduleStatusTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Horizon: %s | Immediate %d | Deferred %d | Archive-aware %d | Follow-up %d | Pinned %d | Immediate window: %s | Deferred window: %s | Blocked follow-up: %s"),
|
||||
CachedCoachScheduleHorizonSummary.CurrentHorizonLabel.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *CachedCoachScheduleHorizonSummary.CurrentHorizonLabel,
|
||||
CachedCoachScheduleHorizonSummary.ImmediateEntryCount,
|
||||
CachedCoachScheduleHorizonSummary.DeferredEntryCount,
|
||||
CachedCoachScheduleHorizonSummary.ArchiveAwareEntryCount,
|
||||
CachedCoachScheduleHorizonSummary.FollowUpEntryCount,
|
||||
CachedCoachScheduleHorizonSummary.PinnedEntryCount,
|
||||
CachedCoachScheduleHorizonSummary.bHasImmediateWindow ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachScheduleHorizonSummary.bHasDeferredWindow ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachScheduleHorizonSummary.bHasBlockedFollowUp ? TEXT("yes") : TEXT("no")
|
||||
)));
|
||||
}
|
||||
if (SchedulePolicyTextBlock != nullptr)
|
||||
{
|
||||
SchedulePolicyTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Policy: immediate %d | pending %d | deferred %d | manual %d | earliest deferred: %s | dominant reason: %s | pressure %.2f | manual allowed: %s | expired deferrals: %s | review needed: %s | archive-aware: %s | blocked follow-up: %s"),
|
||||
CachedCoachSchedulePolicySummary.ImmediateEntryCount,
|
||||
CachedCoachSchedulePolicySummary.PendingEntryCount,
|
||||
CachedCoachSchedulePolicySummary.DeferredEntryCount,
|
||||
CachedCoachSchedulePolicySummary.ManualRescheduleEntryCount,
|
||||
CachedCoachSchedulePolicySummary.EarliestDeferredUntilUtc.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *CachedCoachSchedulePolicySummary.EarliestDeferredUntilUtc,
|
||||
CachedCoachSchedulePolicySummary.DominantDeferralReasonLabel.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *CachedCoachSchedulePolicySummary.DominantDeferralReasonLabel,
|
||||
CachedCoachSchedulePolicySummary.ReschedulePressureScore,
|
||||
CachedCoachSchedulePolicySummary.bAllowsManualReschedule ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachSchedulePolicySummary.bHasExpiredDeferrals ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachSchedulePolicySummary.bNeedsManualRescheduleReview ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals ? TEXT("yes") : TEXT("no"),
|
||||
CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals ? TEXT("yes") : TEXT("no")
|
||||
)));
|
||||
}
|
||||
if (VerificationStatusTextBlock != nullptr)
|
||||
{
|
||||
VerificationStatusTextBlock->SetText(FText::FromString(CachedCoachPanelState.VerificationStatusLine));
|
||||
}
|
||||
if (VerificationHeaderTextBlock != nullptr)
|
||||
{
|
||||
VerificationHeaderTextBlock->SetText(FText::FromString(TEXT("[Repository Verification]")));
|
||||
}
|
||||
if (VerificationDetailTextBlock != nullptr)
|
||||
{
|
||||
VerificationDetailTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Path: %s\nRound-trip pass: %s | Save: %s | Load: %s | Count parity: %s | State parity: %s | Integrity parity: %s\nRuns %d/%d | Learners %d/%d | Reviews %d/%d | Plans %d/%d | Queues %d/%d | History %d/%d | Notes: %s"),
|
||||
CachedRepositoryRoundTripVerification.ResolvedPath.IsEmpty()
|
||||
? TEXT("not yet verified")
|
||||
: *CachedRepositoryRoundTripVerification.ResolvedPath,
|
||||
CachedRepositoryRoundTripVerification.bDidRoundTripPass ? TEXT("yes") : TEXT("no"),
|
||||
CachedRepositoryRoundTripVerification.bSaveSucceeded ? TEXT("yes") : TEXT("no"),
|
||||
CachedRepositoryRoundTripVerification.bLoadSucceeded ? TEXT("yes") : TEXT("no"),
|
||||
CachedRepositoryRoundTripVerification.bRepositoryCountsMatch ? TEXT("yes") : TEXT("no"),
|
||||
CachedRepositoryRoundTripVerification.bSerializedStateMatch ? TEXT("yes") : TEXT("no"),
|
||||
CachedRepositoryRoundTripVerification.bIntegrityReportMatch ? TEXT("yes") : TEXT("no"),
|
||||
CachedRepositoryRoundTripVerification.LoadedRunRecordCount,
|
||||
CachedRepositoryRoundTripVerification.ExpectedRunRecordCount,
|
||||
CachedRepositoryRoundTripVerification.LoadedLearnerStateCount,
|
||||
CachedRepositoryRoundTripVerification.ExpectedLearnerStateCount,
|
||||
CachedRepositoryRoundTripVerification.LoadedReviewPlanCount,
|
||||
CachedRepositoryRoundTripVerification.ExpectedReviewPlanCount,
|
||||
CachedRepositoryRoundTripVerification.LoadedCoachActionPlanCount,
|
||||
CachedRepositoryRoundTripVerification.ExpectedCoachActionPlanCount,
|
||||
CachedRepositoryRoundTripVerification.LoadedCoachSessionQueueCount,
|
||||
CachedRepositoryRoundTripVerification.ExpectedCoachSessionQueueCount,
|
||||
CachedRepositoryRoundTripVerification.LoadedCoachSessionQueueHistoryCount,
|
||||
CachedRepositoryRoundTripVerification.ExpectedCoachSessionQueueHistoryCount,
|
||||
*VerificationNotePreview
|
||||
)));
|
||||
}
|
||||
|
||||
FString PrimaryLabel = CachedCoachPanelState.PrimaryActionLabel;
|
||||
if (PrimaryLabel.IsEmpty())
|
||||
|
|
@ -667,6 +896,22 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
PromoteButton->SetIsEnabled(!FindFirstDeferredQueueEntryId().IsEmpty());
|
||||
}
|
||||
if (CompleteRunButtonLabel != nullptr)
|
||||
{
|
||||
CompleteRunButtonLabel->SetText(FText::FromString(TEXT("Complete Run")));
|
||||
}
|
||||
if (CompleteRunButton != nullptr)
|
||||
{
|
||||
CompleteRunButton->SetIsEnabled(bHasActiveRun);
|
||||
}
|
||||
if (ClearRunButtonLabel != nullptr)
|
||||
{
|
||||
ClearRunButtonLabel->SetText(FText::FromString(TEXT("Clear Run")));
|
||||
}
|
||||
if (ClearRunButton != nullptr)
|
||||
{
|
||||
ClearRunButton->SetIsEnabled(bHasActiveRun);
|
||||
}
|
||||
if (PreviousQueueButtonLabel != nullptr)
|
||||
{
|
||||
PreviousQueueButtonLabel->SetText(FText::FromString(TEXT("Prev Queue")));
|
||||
|
|
|
|||
|
|
@ -59,12 +59,36 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> FocusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> ActiveRunHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> ActiveRunStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> ActiveRunSummaryTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> QueueStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> ScheduleHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> ScheduleStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> SchedulePolicyTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> VerificationStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> VerificationHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> VerificationDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> QueueCountsTextBlock = nullptr;
|
||||
|
||||
|
|
@ -104,6 +128,18 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> PromoteButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> CompleteRunButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> CompleteRunButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> ClearRunButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> ClearRunButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> PreviousQueueButton = nullptr;
|
||||
|
||||
|
|
@ -134,6 +170,12 @@ protected:
|
|||
UFUNCTION()
|
||||
void HandlePromoteClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleCompleteRunClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleClearRunClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePreviousQueueClicked();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue