// Copyright Pacificao. All Rights Reserved. #include "AgrarianMvpFrontendWidget.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/SizeBox.h" #include "Components/Slider.h" #include "Components/TextBlock.h" #include "Components/VerticalBox.h" #include "Components/VerticalBoxSlot.h" #include "GameFramework/PlayerController.h" #include "InputCoreTypes.h" #include "Kismet/GameplayStatics.h" #include "Styling/CoreStyle.h" #include "TimerManager.h" namespace { FButtonStyle MakeAgrarianButtonStyle(const FLinearColor& NormalColor, const FLinearColor& HoveredColor) { FButtonStyle ButtonStyle = FCoreStyle::Get().GetWidgetStyle(TEXT("Button")); ButtonStyle.Normal.TintColor = FSlateColor(NormalColor); ButtonStyle.Hovered.TintColor = FSlateColor(HoveredColor); ButtonStyle.Pressed.TintColor = FSlateColor(FLinearColor( FMath::Min(HoveredColor.R + 0.12f, 1.0f), FMath::Min(HoveredColor.G + 0.12f, 1.0f), FMath::Min(HoveredColor.B + 0.12f, 1.0f), HoveredColor.A)); return ButtonStyle; } } void UAgrarianMvpFrontendWidget::NativeConstruct() { Super::NativeConstruct(); SetIsFocusable(true); RebuildFrontendTree(); FocusPrimaryButton(); } FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) { if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Left || Key == EKeys::A) { PlayUiSound(UiSelectionSound); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); return FReply::Handled(); } if (Key == EKeys::Right || Key == EKeys::D) { PlayUiSound(UiSelectionSound); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); return FReply::Handled(); } if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); return FReply::Handled(); } if (Key == EKeys::BackSpace || Key == EKeys::Escape) { PlayUiSound(UiBackSound); BackFromActiveScreen(); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar || Key == EKeys::Escape) { PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); return FReply::Handled(); } if (Key == EKeys::Q) { PlayUiSound(UiSaveQuitSound); SaveAndQuit(); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit) { return FReply::Handled(); } return Super::NativeOnKeyDown(InGeometry, InKeyEvent); } void UAgrarianMvpFrontendWidget::SetActiveScreen(EAgrarianMvpFrontendScreen NewScreen) { ActiveScreen = NewScreen; RebuildFrontendTree(); FocusPrimaryButton(); } void UAgrarianMvpFrontendWidget::SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype NewArchetype) { SelectedCharacterArchetype = NewArchetype; RebuildFrontendTree(); FocusPrimaryButton(); } void UAgrarianMvpFrontendWidget::SetUiScale(float NewUiScale) { UiScale = FMath::Clamp(NewUiScale, 0.75f, 1.5f); RebuildFrontendTree(); FocusPrimaryButton(); } void UAgrarianMvpFrontendWidget::SetHighContrastMode(bool bNewUseHighContrast) { bUseHighContrast = bNewUseHighContrast; RebuildFrontendTree(); FocusPrimaryButton(); } void UAgrarianMvpFrontendWidget::ConfirmActiveScreen() { ContinueFromActiveScreen(); } void UAgrarianMvpFrontendWidget::BackFromActiveScreen() { ReturnFromActiveScreen(); } void UAgrarianMvpFrontendWidget::SaveAndQuit() { SetActiveScreen(EAgrarianMvpFrontendScreen::SavingAndQuit); if (UWorld* World = GetWorld()) { FTimerHandle SaveAndQuitTimerHandle; World->GetTimerManager().SetTimer(SaveAndQuitTimerHandle, this, &UAgrarianMvpFrontendWidget::ExecuteSaveAndQuit, 0.35f, false); return; } ExecuteSaveAndQuit(); } void UAgrarianMvpFrontendWidget::ExecuteSaveAndQuit() { if (APlayerController* PlayerController = GetOwningPlayer()) { PlayerController->ConsoleCommand(TEXT("AgrarianSaveWorld")); PlayerController->ConsoleCommand(TEXT("quit")); } } void UAgrarianMvpFrontendWidget::ContinueFromActiveScreen() { if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection) { SetActiveScreen(EAgrarianMvpFrontendScreen::JoinServer); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { SetActiveScreen(EAgrarianMvpFrontendScreen::Loading); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) { CompleteFrontendFlow(); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit) { return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { CompleteFrontendFlow(); return; } } void UAgrarianMvpFrontendWidget::ReturnFromActiveScreen() { if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { CompleteFrontendFlow(); } } void UAgrarianMvpFrontendWidget::CompleteFrontendFlow() { if (APlayerController* PlayerController = GetOwningPlayer()) { if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) { PlayerController->ConsoleCommand(SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale ? TEXT("AgrarianSelectCharacter female") : TEXT("AgrarianSelectCharacter male")); } PlayerController->SetInputMode(FInputModeGameOnly()); PlayerController->bShowMouseCursor = false; PlayerController->SetIgnoreMoveInput(false); PlayerController->SetIgnoreLookInput(false); } RemoveFromParent(); } void UAgrarianMvpFrontendWidget::RebuildFrontendTree() { if (!WidgetTree) { return; } PrimaryFocusButton = nullptr; const float Scale = FMath::Clamp(UiScale, 0.75f, 1.5f); const FLinearColor BackdropColor = bUseHighContrast ? FLinearColor::Black : FLinearColor(0.015f, 0.018f, 0.014f, 0.99f); const FLinearColor PanelColor = bUseHighContrast ? FLinearColor(0.0f, 0.0f, 0.0f, 0.98f) : FLinearColor(0.035f, 0.045f, 0.034f, 0.96f); const FLinearColor AccentColor = bUseHighContrast ? FLinearColor(0.95f, 0.95f, 0.30f, 1.0f) : FLinearColor(0.45f, 0.72f, 0.40f, 1.0f); const FLinearColor TextColor = bUseHighContrast ? FLinearColor::White : FLinearColor(0.92f, 0.98f, 0.84f, 1.0f); const FLinearColor MutedTextColor = bUseHighContrast ? FLinearColor(0.86f, 0.86f, 0.66f, 1.0f) : FLinearColor(0.72f, 0.80f, 0.68f, 1.0f); const FLinearColor ButtonColor = bUseHighContrast ? FLinearColor(0.30f, 0.30f, 0.02f, 1.0f) : FLinearColor(0.22f, 0.40f, 0.18f, 0.98f); const FLinearColor ButtonHoverColor = bUseHighContrast ? FLinearColor(0.48f, 0.48f, 0.08f, 1.0f) : FLinearColor(0.35f, 0.58f, 0.30f, 1.0f); const FLinearColor SecondaryButtonColor = bUseHighContrast ? FLinearColor(0.12f, 0.12f, 0.12f, 1.0f) : FLinearColor(0.10f, 0.13f, 0.09f, 0.98f); const FLinearColor QuitButtonColor = bUseHighContrast ? FLinearColor(0.32f, 0.10f, 0.08f, 1.0f) : FLinearColor(0.42f, 0.22f, 0.18f, 0.98f); UBorder* RootBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("MvpFrontendRoot")); RootBorder->SetBrushColor(BackdropColor); RootBorder->SetPadding(FMargin(24.0f * Scale)); RootBorder->SetHorizontalAlignment(HAlign_Center); RootBorder->SetVerticalAlignment(VAlign_Center); WidgetTree->RootWidget = RootBorder; USizeBox* PanelSizeBox = WidgetTree->ConstructWidget(USizeBox::StaticClass(), TEXT("MvpFrontendPanelSize")); PanelSizeBox->SetWidthOverride(780.0f * Scale); PanelSizeBox->SetMinDesiredWidth(420.0f); PanelSizeBox->SetMinDesiredHeight(300.0f); RootBorder->SetContent(PanelSizeBox); UBorder* PanelBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("MvpFrontendPanel")); PanelBorder->SetBrushColor(PanelColor); PanelBorder->SetPadding(FMargin(42.0f * Scale, 34.0f * Scale)); PanelSizeBox->SetContent(PanelBorder); UVerticalBox* Panel = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("MvpFrontendStack")); PanelBorder->SetContent(Panel); if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { AddText(Panel, FText::FromString(TEXT("Pause Menu")), FMath::RoundToInt(20.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, MainMenuTitle, FMath::RoundToInt(54.0f * Scale), true, TextColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Gameplay is paused while this menu is active.")), FMath::RoundToInt(22.0f * Scale), false, MutedTextColor, 72.0f * Scale); PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Resume")), ButtonColor, ButtonHoverColor, 16.0f * Scale); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked); PrimaryFocusButton->OnHovered.AddDynamic(this, &UAgrarianMvpFrontendWidget::FocusPrimaryButton); UButton* QuitButton = AddButton(Panel, FText::FromString(TEXT("Save & Quit")), QuitButtonColor, FLinearColor(0.58f, 0.28f, 0.22f, 1.0f), 34.0f * Scale); QuitButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked); AddText(Panel, FText::FromString(TEXT("Audio")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 8.0f * Scale); if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Master")), MasterVolume, 8.0f * Scale)) { Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleMasterVolumeChanged); } if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Ambient")), AmbientVolume, 8.0f * Scale)) { Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleAmbientVolumeChanged); } if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Weather")), WeatherVolume, 8.0f * Scale)) { Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleWeatherVolumeChanged); } if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Effects")), EffectsVolume, 8.0f * Scale)) { Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleEffectsVolumeChanged); } if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("Wildlife")), WildlifeVolume, 8.0f * Scale)) { Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleWildlifeVolumeChanged); } if (USlider* Slider = AddVolumeSlider(Panel, FText::FromString(TEXT("UI")), UiVolume, 24.0f * Scale)) { Slider->OnValueChanged.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleUiVolumeChanged); } AddText(Panel, FText::FromString(TEXT("Escape opens this menu. Save & Quit writes the current world save before closing.")), FMath::RoundToInt(16.0f * Scale), false, MutedTextColor, 0.0f); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection) { AddText(Panel, FText::FromString(TEXT("Character Selection")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Choose your first pioneer")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Select the person who will step into Ground Zero.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 24.0f * Scale); UHorizontalBox* CharacterRow = WidgetTree->ConstructWidget(UHorizontalBox::StaticClass(), TEXT("CharacterRow")); if (UVerticalBoxSlot* RowSlot = Panel->AddChildToVerticalBox(CharacterRow)) { RowSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 22.0f * Scale)); } const bool bMaleSelected = SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultMale; const bool bFemaleSelected = SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale; UButton* MaleButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("MalePioneerButton")); MaleButton->SetStyle(MakeAgrarianButtonStyle(bMaleSelected ? ButtonHoverColor : SecondaryButtonColor, ButtonHoverColor)); MaleButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleMaleCharacterClicked); UVerticalBox* MaleStack = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("MalePioneerStack")); MaleButton->SetContent(MaleStack); AddText(MaleStack, FText::FromString(TEXT("Young adult male")), FMath::RoundToInt(21.0f * Scale), true, TextColor, 8.0f * Scale); AddText(MaleStack, FText::FromString(TEXT("Average build, practical workwear proxy, survival baseline.")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 18.0f * Scale); AddText(MaleStack, bMaleSelected ? FText::FromString(TEXT("Selected")) : FText::FromString(TEXT("Available")), FMath::RoundToInt(15.0f * Scale), true, bMaleSelected ? AccentColor : MutedTextColor, 0.0f); if (UHorizontalBoxSlot* MaleSlot = CharacterRow->AddChildToHorizontalBox(MaleButton)) { MaleSlot->SetPadding(FMargin(0.0f, 0.0f, 12.0f * Scale, 0.0f)); MaleSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); } UButton* FemaleButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("FemalePioneerButton")); FemaleButton->SetStyle(MakeAgrarianButtonStyle(bFemaleSelected ? ButtonHoverColor : SecondaryButtonColor, ButtonHoverColor)); FemaleButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleFemaleCharacterClicked); UVerticalBox* FemaleStack = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("FemalePioneerStack")); FemaleButton->SetContent(FemaleStack); AddText(FemaleStack, FText::FromString(TEXT("Young adult female")), FMath::RoundToInt(21.0f * Scale), true, TextColor, 8.0f * Scale); AddText(FemaleStack, FText::FromString(TEXT("Average build, practical workwear proxy, survival baseline.")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 18.0f * Scale); AddText(FemaleStack, bFemaleSelected ? FText::FromString(TEXT("Selected")) : FText::FromString(TEXT("Available")), FMath::RoundToInt(15.0f * Scale), true, bFemaleSelected ? AccentColor : MutedTextColor, 0.0f); if (UHorizontalBoxSlot* FemaleSlot = CharacterRow->AddChildToHorizontalBox(FemaleButton)) { FemaleSlot->SetPadding(FMargin(12.0f * Scale, 0.0f, 0.0f, 0.0f)); FemaleSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); } PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Continue")), ButtonColor, ButtonHoverColor, 10.0f * Scale); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked); PrimaryFocusButton->OnHovered.AddDynamic(this, &UAgrarianMvpFrontendWidget::FocusPrimaryButton); AddText(Panel, FText::Format(FText::FromString(TEXT("Selected {0}: {1}. Click a card or use Left/Right, then continue.")), GetSelectedRoleLabel(), GetSelectedCharacterLabel()), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 0.0f); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { AddText(Panel, FText::FromString(TEXT("Server Join")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Join MVP server")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale); AddText(Panel, FText::Format(FText::FromString(TEXT("{0}: {1}")), GetSelectedRoleLabel(), GetSelectedCharacterLabel()), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 36.0f * Scale); AddText(Panel, FText::FromString(TEXT("Server address")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 4.0f * Scale); AddText(Panel, JoinServerAddress, FMath::RoundToInt(24.0f * Scale), true, TextColor, 42.0f * Scale); UHorizontalBox* ButtonRow = WidgetTree->ConstructWidget(UHorizontalBox::StaticClass(), TEXT("JoinButtonRow")); if (UVerticalBoxSlot* RowSlot = Panel->AddChildToVerticalBox(ButtonRow)) { RowSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 18.0f * Scale)); } PrimaryFocusButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("ContinueToLoadingButton")); PrimaryFocusButton->SetStyle(MakeAgrarianButtonStyle(ButtonColor, ButtonHoverColor)); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked); PrimaryFocusButton->OnHovered.AddDynamic(this, &UAgrarianMvpFrontendWidget::FocusPrimaryButton); UTextBlock* ContinueLabel = AddText(nullptr, FText::FromString(TEXT("Continue to loading")), FMath::RoundToInt(20.0f * Scale), true, TextColor, 0.0f); PrimaryFocusButton->SetContent(ContinueLabel); if (UButtonSlot* ContinueLabelSlot = Cast(ContinueLabel->Slot)) { ContinueLabelSlot->SetPadding(FMargin(22.0f, 12.0f)); ContinueLabelSlot->SetHorizontalAlignment(HAlign_Center); } if (UHorizontalBoxSlot* ContinueSlot = ButtonRow->AddChildToHorizontalBox(PrimaryFocusButton)) { ContinueSlot->SetPadding(FMargin(0.0f, 0.0f, 12.0f * Scale, 0.0f)); ContinueSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); } UButton* BackButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("BackButton")); BackButton->SetStyle(MakeAgrarianButtonStyle(SecondaryButtonColor, ButtonHoverColor)); BackButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleBackClicked); UTextBlock* BackLabel = AddText(nullptr, FText::FromString(TEXT("Back")), FMath::RoundToInt(20.0f * Scale), true, TextColor, 0.0f); BackButton->SetContent(BackLabel); if (UButtonSlot* BackLabelSlot = Cast(BackLabel->Slot)) { BackLabelSlot->SetPadding(FMargin(22.0f, 12.0f)); BackLabelSlot->SetHorizontalAlignment(HAlign_Center); } if (UHorizontalBoxSlot* BackSlot = ButtonRow->AddChildToHorizontalBox(BackButton)) { BackSlot->SetPadding(FMargin(12.0f * Scale, 0.0f, 0.0f, 0.0f)); BackSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); } AddText(Panel, FText::FromString(TEXT("Click continue, press Enter, or press Backspace/Escape to revise your pioneer.")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 0.0f); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit) { AddText(Panel, FText::FromString(TEXT("Saving World")), FMath::RoundToInt(20.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Writing the current world state")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale); AddText(Panel, FText::FromString(TEXT("The demo will close after the save command is issued.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 0.0f); return; } AddText(Panel, FText::FromString(TEXT("Loading Segment")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Preparing Ground Zero")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale); AddText(Panel, FText::FromString(TEXT("Loading terrain, weather, survival state, and server session data.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 70.0f * Scale); AddText(Panel, FText::Format(FText::FromString(TEXT("{0}: {1} | Server: {2}")), GetSelectedRoleLabel(), GetSelectedCharacterLabel(), JoinServerAddress), FMath::RoundToInt(18.0f * Scale), false, TextColor, 34.0f * Scale); PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Enter Ground Zero")), ButtonColor, ButtonHoverColor, 14.0f * Scale); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked); PrimaryFocusButton->OnHovered.AddDynamic(this, &UAgrarianMvpFrontendWidget::FocusPrimaryButton); AddText(Panel, FText::FromString(TEXT("Click or press Enter to close the MVP menu and begin testing.")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 0.0f); } UTextBlock* UAgrarianMvpFrontendWidget::AddText(UVerticalBox* Parent, const FText& Text, int32 FontSize, bool bBold, const FLinearColor& Color, float BottomPadding) { UTextBlock* TextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); TextBlock->SetText(Text); TextBlock->SetFont(FCoreStyle::GetDefaultFontStyle(bBold ? "Bold" : "Regular", FontSize)); TextBlock->SetColorAndOpacity(FSlateColor(Color)); TextBlock->SetAutoWrapText(true); TextBlock->SetJustification(ETextJustify::Left); if (Parent) { if (UVerticalBoxSlot* TextSlot = Parent->AddChildToVerticalBox(TextBlock)) { TextSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, BottomPadding)); } } return TextBlock; } UButton* UAgrarianMvpFrontendWidget::AddButton(UVerticalBox* Parent, const FText& Text, const FLinearColor& NormalColor, const FLinearColor& HoveredColor, float BottomPadding) { UButton* Button = WidgetTree->ConstructWidget(UButton::StaticClass()); Button->SetStyle(MakeAgrarianButtonStyle(NormalColor, HoveredColor)); Button->SetClickMethod(EButtonClickMethod::MouseDown); Button->SetTouchMethod(EButtonTouchMethod::Down); if (!Text.IsEmpty()) { UTextBlock* Label = AddText(nullptr, Text, FMath::RoundToInt(20.0f * FMath::Clamp(UiScale, 0.75f, 1.5f)), true, FLinearColor(0.96f, 1.0f, 0.90f, 1.0f), 0.0f); Button->SetContent(Label); if (UButtonSlot* LabelSlot = Cast(Label->Slot)) { LabelSlot->SetPadding(FMargin(22.0f, 12.0f)); LabelSlot->SetHorizontalAlignment(HAlign_Center); } } if (Parent) { if (UVerticalBoxSlot* ButtonSlot = Parent->AddChildToVerticalBox(Button)) { ButtonSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, BottomPadding)); ButtonSlot->SetHorizontalAlignment(HAlign_Left); } } return Button; } void UAgrarianMvpFrontendWidget::PlayUiSound(USoundBase* Sound) const { if (Sound) { UGameplayStatics::PlaySound2D(this, Sound, FMath::Clamp(MasterVolume * UiVolume, 0.0f, 1.0f)); } } USlider* UAgrarianMvpFrontendWidget::AddVolumeSlider(UVerticalBox* Parent, const FText& Label, float Value, float BottomPadding) { if (!Parent || !WidgetTree) { return nullptr; } UHorizontalBox* Row = WidgetTree->ConstructWidget(UHorizontalBox::StaticClass()); if (UVerticalBoxSlot* RowSlot = Parent->AddChildToVerticalBox(Row)) { RowSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, BottomPadding)); RowSlot->SetHorizontalAlignment(HAlign_Fill); } UTextBlock* LabelText = AddText(nullptr, Label, 15, false, FLinearColor(0.82f, 0.90f, 0.76f, 1.0f), 0.0f); if (UHorizontalBoxSlot* LabelSlot = Row->AddChildToHorizontalBox(LabelText)) { LabelSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); LabelSlot->SetHorizontalAlignment(HAlign_Left); LabelSlot->SetVerticalAlignment(VAlign_Center); } USlider* Slider = WidgetTree->ConstructWidget(USlider::StaticClass()); Slider->SetValue(FMath::Clamp(Value, 0.0f, 1.0f)); if (UHorizontalBoxSlot* SliderSlot = Row->AddChildToHorizontalBox(Slider)) { SliderSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); SliderSlot->SetHorizontalAlignment(HAlign_Fill); SliderSlot->SetVerticalAlignment(VAlign_Center); } return Slider; } void UAgrarianMvpFrontendWidget::HandleMasterVolumeChanged(float Value) { MasterVolume = FMath::Clamp(Value, 0.0f, 1.0f); } void UAgrarianMvpFrontendWidget::HandleAmbientVolumeChanged(float Value) { AmbientVolume = FMath::Clamp(Value, 0.0f, 1.0f); } void UAgrarianMvpFrontendWidget::HandleWeatherVolumeChanged(float Value) { WeatherVolume = FMath::Clamp(Value, 0.0f, 1.0f); } void UAgrarianMvpFrontendWidget::HandleEffectsVolumeChanged(float Value) { EffectsVolume = FMath::Clamp(Value, 0.0f, 1.0f); } void UAgrarianMvpFrontendWidget::HandleWildlifeVolumeChanged(float Value) { WildlifeVolume = FMath::Clamp(Value, 0.0f, 1.0f); } void UAgrarianMvpFrontendWidget::HandleUiVolumeChanged(float Value) { UiVolume = FMath::Clamp(Value, 0.0f, 1.0f); } void UAgrarianMvpFrontendWidget::FocusPrimaryButton() { if (PrimaryFocusButton) { PrimaryFocusButton->SetKeyboardFocus(); } else { SetKeyboardFocus(); } } void UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked() { PlayUiSound(UiConfirmSound); ConfirmActiveScreen(); } void UAgrarianMvpFrontendWidget::HandleBackClicked() { PlayUiSound(UiBackSound); BackFromActiveScreen(); } void UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked() { PlayUiSound(UiSaveQuitSound); SaveAndQuit(); } void UAgrarianMvpFrontendWidget::HandleMaleCharacterClicked() { PlayUiSound(UiSelectionSound); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); } void UAgrarianMvpFrontendWidget::HandleFemaleCharacterClicked() { PlayUiSound(UiSelectionSound); SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); } FText UAgrarianMvpFrontendWidget::GetSelectedCharacterLabel() const { return SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale ? FText::FromString(TEXT("young adult female")) : FText::FromString(TEXT("young adult male")); } FText UAgrarianMvpFrontendWidget::GetSelectedRoleLabel() const { return FText::FromString(TEXT("Pioneer")); }