hypertwist/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.cpp
2026-07-23 17:23:24 +00:00

449 lines
13 KiB
C++

#include "HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.h"
#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/Button.h"
#include "Components/ButtonSlot.h"
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/ScrollBox.h"
#include "Components/SizeBox.h"
#include "Components/TextBlock.h"
#include "Components/UniformGridPanel.h"
#include "Components/UniformGridSlot.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "GameFramework/GameModeBase.h"
#include "HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h"
namespace HyperTwistClassicCubeHUDWidgetInternal
{
const FLinearColor PanelColor(0.012f, 0.025f, 0.045f, 0.94f);
const FLinearColor AccentColor(0.15f, 0.90f, 0.95f, 1.0f);
const FLinearColor PrimaryTextColor(0.92f, 0.97f, 1.0f, 1.0f);
const FLinearColor MutedTextColor(0.58f, 0.70f, 0.78f, 1.0f);
const FLinearColor ButtonColor(0.04f, 0.16f, 0.23f, 1.0f);
UObject* ResolveOperatorSurfaceObject(const UUserWidget* Widget)
{
if (Widget == nullptr || Widget->GetWorld() == nullptr)
{
return nullptr;
}
AGameModeBase* GameMode = Widget->GetWorld()->GetAuthGameMode();
if (GameMode == nullptr
|| !GameMode->GetClass()->ImplementsInterface(UHyperTwistClassicCubeOperatorSurface::StaticClass()))
{
return nullptr;
}
return GameMode;
}
}
void UHyperTwistClassicCubeHUDWidget::NativeOnInitialized()
{
Super::NativeOnInitialized();
EnsureWidgetTreeBuilt();
}
void UHyperTwistClassicCubeHUDWidget::NativeConstruct()
{
Super::NativeConstruct();
EnsureWidgetTreeBuilt();
}
TSharedRef<SWidget> UHyperTwistClassicCubeHUDWidget::RebuildWidget()
{
Initialize();
EnsureWidgetTreeBuilt();
return Super::RebuildWidget();
}
bool UHyperTwistClassicCubeHUDWidget::PrepareClassicCubeHudSurface()
{
EnsureWidgetTreeBuilt();
return IsClassicCubeHudSurfaceReady();
}
bool UHyperTwistClassicCubeHUDWidget::IsClassicCubeHudSurfaceReady() const
{
return WidgetTree != nullptr
&& WidgetTree->RootWidget != nullptr
&& StatusTextBlock != nullptr
&& TimerTextBlock != nullptr
&& NewScrambleButton != nullptr
&& HintButton != nullptr;
}
void UHyperTwistClassicCubeHUDWidget::SetHudLines(
const FString& InStatusLine,
const FString& InScrambleLine,
const FString& InTimerLine,
const FString& InInspectionLine,
const FString& InResultLine,
const FString& InControlsLine,
const FString& InHintLine,
const FString& InSolutionLine,
const FString& InReplayLine,
const FString& InLeaderboardLine,
const FString& InModeLine,
const FString& InTrainingLine
)
{
EnsureWidgetTreeBuilt();
if (StatusTextBlock != nullptr)
{
StatusTextBlock->SetText(FText::FromString(InStatusLine));
}
if (ScrambleTextBlock != nullptr)
{
ScrambleTextBlock->SetText(FText::FromString(InScrambleLine));
}
if (TimerTextBlock != nullptr)
{
TimerTextBlock->SetText(FText::FromString(InTimerLine));
}
if (InspectionTextBlock != nullptr)
{
InspectionTextBlock->SetText(FText::FromString(InInspectionLine));
}
if (ResultTextBlock != nullptr)
{
ResultTextBlock->SetText(FText::FromString(InResultLine));
}
if (ControlsTextBlock != nullptr)
{
ControlsTextBlock->SetText(FText::FromString(InControlsLine));
}
if (HintTextBlock != nullptr)
{
HintTextBlock->SetText(FText::FromString(InHintLine));
}
if (SolutionTextBlock != nullptr)
{
SolutionTextBlock->SetText(FText::FromString(InSolutionLine));
}
if (ReplayTextBlock != nullptr)
{
ReplayTextBlock->SetText(FText::FromString(InReplayLine));
}
if (LeaderboardTextBlock != nullptr)
{
LeaderboardTextBlock->SetText(FText::FromString(InLeaderboardLine));
}
if (ModeTextBlock != nullptr)
{
ModeTextBlock->SetText(FText::FromString(InModeLine));
}
if (TrainingTextBlock != nullptr)
{
TrainingTextBlock->SetText(FText::FromString(InTrainingLine));
TrainingTextBlock->SetVisibility(
InTrainingLine.IsEmpty()
? ESlateVisibility::Collapsed
: ESlateVisibility::HitTestInvisible);
}
}
bool UHyperTwistClassicCubeHUDWidget::IsNewScrambleButtonHovered() const
{
return bNewScrambleButtonHovered || (NewScrambleButton != nullptr && NewScrambleButton->IsHovered());
}
bool UHyperTwistClassicCubeHUDWidget::IsAnyActionButtonHovered() const
{
auto IsHovered = [](const UButton* Button)
{
return Button != nullptr && Button->IsHovered();
};
return IsHovered(NewScrambleButton)
|| IsHovered(HintButton)
|| IsHovered(SubmitSolveButton)
|| IsHovered(ModeToggleButton);
}
void UHyperTwistClassicCubeHUDWidget::SetModeToggleButtonLabel(const FString& InLabel)
{
EnsureWidgetTreeBuilt();
if (ModeToggleButtonLabel != nullptr)
{
ModeToggleButtonLabel->SetText(FText::FromString(InLabel));
}
}
void UHyperTwistClassicCubeHUDWidget::EnsureWidgetTreeBuilt()
{
if (WidgetTree == nullptr || WidgetTree->RootWidget != nullptr)
{
return;
}
UHorizontalBox* ViewportLayout = WidgetTree->ConstructWidget<UHorizontalBox>(
UHorizontalBox::StaticClass(),
TEXT("ClassicCubeHudRoot")
);
ViewportLayout->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
WidgetTree->RootWidget = ViewportLayout;
USizeBox* PanelSize = WidgetTree->ConstructWidget<USizeBox>(
USizeBox::StaticClass(),
TEXT("ClassicCubeHudPanelSize")
);
PanelSize->SetWidthOverride(520.0f);
PanelSize->SetMaxDesiredHeight(1000.0f);
if (UHorizontalBoxSlot* PanelSlot = ViewportLayout->AddChildToHorizontalBox(PanelSize))
{
PanelSlot->SetPadding(FMargin(18.0f));
PanelSlot->SetVerticalAlignment(VAlign_Top);
}
UBorder* PanelBorder = WidgetTree->ConstructWidget<UBorder>(
UBorder::StaticClass(),
TEXT("ClassicCubeHudPanel")
);
PanelBorder->SetPadding(FMargin(18.0f));
PanelBorder->SetBrushColor(HyperTwistClassicCubeHUDWidgetInternal::PanelColor);
PanelSize->AddChild(PanelBorder);
UScrollBox* PanelScroll = WidgetTree->ConstructWidget<UScrollBox>(
UScrollBox::StaticClass(),
TEXT("ClassicCubeHudScroll")
);
PanelBorder->AddChild(PanelScroll);
UVerticalBox* RootLayout = WidgetTree->ConstructWidget<UVerticalBox>(
UVerticalBox::StaticClass(),
TEXT("ClassicCubeHudContent")
);
PanelScroll->AddChild(RootLayout);
UTextBlock* TitleText = AddLine(
RootLayout,
TEXT("ClassicCubeHudTitle"),
20,
HyperTwistClassicCubeHUDWidgetInternal::AccentColor
);
if (TitleText != nullptr)
{
TitleText->SetText(FText::FromString(TEXT("CLASSIC CUBE // TRAINING")));
}
StatusTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudStatus"), 15, HyperTwistClassicCubeHUDWidgetInternal::PrimaryTextColor);
TimerTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudTimer"), 27, HyperTwistClassicCubeHUDWidgetInternal::PrimaryTextColor);
InspectionTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudInspection"), 14, HyperTwistClassicCubeHUDWidgetInternal::AccentColor);
ScrambleTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudScramble"), 13, HyperTwistClassicCubeHUDWidgetInternal::MutedTextColor);
ResultTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudResult"), 14, HyperTwistClassicCubeHUDWidgetInternal::PrimaryTextColor);
TrainingTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudTraining"), 13, HyperTwistClassicCubeHUDWidgetInternal::AccentColor);
HintTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudHint"), 13, HyperTwistClassicCubeHUDWidgetInternal::MutedTextColor);
SolutionTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudSolution"), 13, HyperTwistClassicCubeHUDWidgetInternal::MutedTextColor);
ModeTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudMode"), 13, HyperTwistClassicCubeHUDWidgetInternal::PrimaryTextColor);
ReplayTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudReplay"), 12, HyperTwistClassicCubeHUDWidgetInternal::MutedTextColor);
LeaderboardTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudLeaderboard"), 12, HyperTwistClassicCubeHUDWidgetInternal::MutedTextColor);
ControlsTextBlock = AddLine(RootLayout, TEXT("ClassicCubeHudControls"), 12, HyperTwistClassicCubeHUDWidgetInternal::MutedTextColor);
UUniformGridPanel* ActionGrid = WidgetTree->ConstructWidget<UUniformGridPanel>(
UUniformGridPanel::StaticClass(),
TEXT("ClassicCubeHudActionGrid")
);
ActionGrid->SetMinDesiredSlotWidth(220.0f);
if (UVerticalBoxSlot* GridSlot = RootLayout->AddChildToVerticalBox(ActionGrid))
{
GridSlot->SetPadding(FMargin(0.0f, 12.0f, 0.0f, 0.0f));
}
NewScrambleButton = CreateActionButton(
ActionGrid,
TEXT("ClassicCubeHudNewScrambleButton"),
TEXT("ClassicCubeHudNewScrambleButtonLabel"),
NewScrambleButtonLabel,
TEXT("New Scramble"),
0,
0
);
HintButton = CreateActionButton(
ActionGrid,
TEXT("ClassicCubeHudHintButton"),
TEXT("ClassicCubeHudHintButtonLabel"),
HintButtonLabel,
TEXT("Hint"),
0,
1
);
SubmitSolveButton = CreateActionButton(
ActionGrid,
TEXT("ClassicCubeHudSubmitSolveButton"),
TEXT("ClassicCubeHudSubmitSolveButtonLabel"),
SubmitSolveButtonLabel,
TEXT("Submit Solve"),
1,
0
);
ModeToggleButton = CreateActionButton(
ActionGrid,
TEXT("ClassicCubeHudModeToggleButton"),
TEXT("ClassicCubeHudModeToggleButtonLabel"),
ModeToggleButtonLabel,
TEXT("Follow Along"),
1,
1
);
if (NewScrambleButton != nullptr)
{
NewScrambleButton->OnClicked.AddDynamic(this, &UHyperTwistClassicCubeHUDWidget::HandleNewScrambleClicked);
}
if (HintButton != nullptr)
{
HintButton->OnClicked.AddDynamic(this, &UHyperTwistClassicCubeHUDWidget::HandleHintClicked);
}
if (SubmitSolveButton != nullptr)
{
SubmitSolveButton->OnClicked.AddDynamic(this, &UHyperTwistClassicCubeHUDWidget::HandleSubmitSolveClicked);
}
if (ModeToggleButton != nullptr)
{
ModeToggleButton->OnClicked.AddDynamic(this, &UHyperTwistClassicCubeHUDWidget::HandleModeToggleClicked);
}
SetHudLines(
TEXT("status: preparing classic cube lane"),
TEXT("scramble: pending"),
TEXT("timer: idle"),
TEXT("inspection: pending"),
TEXT("result: waiting for first attempt"),
TEXT("controls: LMB clockwise, RMB counter-clockwise, touch clockwise, MMB drag orbit, wheel zoom, R scramble, H hint, Enter submit, F mode, F3 coach, Esc settings"),
TEXT("hint: request solver guidance"),
TEXT("solution: unavailable"),
TEXT("replay: armed for local capture"),
TEXT("leaderboard: no local best yet"),
TEXT("mode: free play"),
FString()
);
}
UTextBlock* UHyperTwistClassicCubeHUDWidget::AddLine(
UVerticalBox* Parent,
const TCHAR* WidgetName,
const int32 FontSize,
const FLinearColor& Color
)
{
if (WidgetTree == nullptr || Parent == nullptr)
{
return nullptr;
}
UTextBlock* TextBlock =
WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), WidgetName);
if (TextBlock == nullptr)
{
return nullptr;
}
TextBlock->SetAutoWrapText(true);
TextBlock->SetColorAndOpacity(FSlateColor(Color));
FSlateFontInfo Font = TextBlock->GetFont();
Font.Size = FontSize;
TextBlock->SetFont(Font);
if (UVerticalBoxSlot* VerticalBoxSlot = Parent->AddChildToVerticalBox(TextBlock))
{
VerticalBoxSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 4.0f));
}
return TextBlock;
}
UButton* UHyperTwistClassicCubeHUDWidget::CreateActionButton(
UUniformGridPanel* Parent,
const TCHAR* ButtonName,
const TCHAR* LabelName,
TObjectPtr<UTextBlock>& OutLabel,
const FString& InitialLabel,
const int32 Row,
const int32 Column
)
{
if (WidgetTree == nullptr || Parent == nullptr)
{
return nullptr;
}
UButton* ActionButton = WidgetTree->ConstructWidget<UButton>(
UButton::StaticClass(),
ButtonName
);
if (ActionButton == nullptr)
{
return nullptr;
}
ActionButton->SetBackgroundColor(HyperTwistClassicCubeHUDWidgetInternal::ButtonColor);
OutLabel = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), LabelName);
if (OutLabel != nullptr)
{
OutLabel->SetText(FText::FromString(InitialLabel));
OutLabel->SetAutoWrapText(true);
OutLabel->SetJustification(ETextJustify::Center);
OutLabel->SetColorAndOpacity(FSlateColor(HyperTwistClassicCubeHUDWidgetInternal::PrimaryTextColor));
FSlateFontInfo Font = OutLabel->GetFont();
Font.Size = 13;
OutLabel->SetFont(Font);
ActionButton->AddChild(OutLabel);
}
Parent->AddChildToUniformGrid(ActionButton, Row, Column);
if (UButtonSlot* ButtonSlot = Cast<UButtonSlot>(OutLabel != nullptr ? OutLabel->Slot : nullptr))
{
ButtonSlot->SetPadding(FMargin(12.0f, 6.0f, 12.0f, 6.0f));
ButtonSlot->SetHorizontalAlignment(HAlign_Center);
ButtonSlot->SetVerticalAlignment(VAlign_Center);
}
return ActionButton;
}
void UHyperTwistClassicCubeHUDWidget::HandleNewScrambleClicked()
{
bNewScrambleButtonHovered = false;
if (UObject* OperatorSurface =
HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this))
{
IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeFreshAttempt(
OperatorSurface
);
}
}
void UHyperTwistClassicCubeHUDWidget::HandleHintClicked()
{
if (UObject* OperatorSurface =
HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this))
{
IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeHint(OperatorSurface);
}
}
void UHyperTwistClassicCubeHUDWidget::HandleSubmitSolveClicked()
{
if (UObject* OperatorSurface =
HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this))
{
IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeSubmitSolve(
OperatorSurface
);
}
}
void UHyperTwistClassicCubeHUDWidget::HandleModeToggleClicked()
{
if (UObject* OperatorSurface =
HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this))
{
IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeToggleFollowAlongMode(
OperatorSurface
);
}
}