From 3aac902142d96d62229d191a450df2faa7ddca08 Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 18 May 2026 22:14:17 -0700 Subject: [PATCH] Fix MVP menu startup and mouse flow --- .../verify_mvp_character_archetype_choice.py | 5 +- .../verify_mvp_character_selection_landing.py | 7 +- Scripts/verify_mvp_join_server_screen.py | 2 + Scripts/verify_mvp_loading_screen.py | 3 +- Scripts/verify_startup_credits_sequence.py | 11 + .../AgrarianGame/AgrarianDemoNoticeWidget.cpp | 35 +++ .../AgrarianGamePlayerController.cpp | 41 ++- .../AgrarianGamePlayerController.h | 6 + .../AgrarianMvpFrontendWidget.cpp | 238 ++++++++++++++++-- .../AgrarianGame/AgrarianMvpFrontendWidget.h | 8 + 10 files changed, 321 insertions(+), 35 deletions(-) diff --git a/Scripts/verify_mvp_character_archetype_choice.py b/Scripts/verify_mvp_character_archetype_choice.py index a68f5bf..eee76be 100644 --- a/Scripts/verify_mvp_character_archetype_choice.py +++ b/Scripts/verify_mvp_character_archetype_choice.py @@ -25,8 +25,11 @@ EXPECTED = { "EKeys::A", "EKeys::D", "Selected", - "Use Left/Right or A/D to choose", + "Click a card or use Left/Right", "GetSelectedCharacterLabel", + "NativeOnMouseButtonDown", + "IsPointInside(LocalMousePosition, MaleCardPosition, CardSize)", + "IsPointInside(LocalMousePosition, FemaleCardPosition, CardSize)", ], "AgrarianGamePlayerController.h": [ "AgrarianSelectCharacter", diff --git a/Scripts/verify_mvp_character_selection_landing.py b/Scripts/verify_mvp_character_selection_landing.py index f5269c9..817e1a3 100644 --- a/Scripts/verify_mvp_character_selection_landing.py +++ b/Scripts/verify_mvp_character_selection_landing.py @@ -17,13 +17,16 @@ EXPECTED = { "AgrarianMvpFrontendWidget.cpp": [ "EAgrarianMvpFrontendScreen::CharacterSelection", "DrawCharacterSelection", - "Choose your first settler", + "Choose your first pioneer", "Young adult male", "Young adult female", - "MVP placeholder landing page", + "Select the person who will step into Ground Zero.", + "NativeOnMouseButtonDown", + "Continue", ], "AgrarianGamePlayerController.cpp": [ "SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection)", + "MvpFrontendStartupDelaySeconds", ], "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ "[x] After splash/startup screens, land on an MVP character selection landing page.", diff --git a/Scripts/verify_mvp_join_server_screen.py b/Scripts/verify_mvp_join_server_screen.py index f797a52..cbe1202 100644 --- a/Scripts/verify_mvp_join_server_screen.py +++ b/Scripts/verify_mvp_join_server_screen.py @@ -22,6 +22,8 @@ EXPECTED = { "SetActiveScreen(EAgrarianMvpFrontendScreen::JoinServer)", "Join MVP server", "Continue to loading", + "Back", + "NativeOnMouseButtonDown", ], "AgrarianGamePlayerController.h": [ "AgrarianShowMvpScreen", diff --git a/Scripts/verify_mvp_loading_screen.py b/Scripts/verify_mvp_loading_screen.py index 1cde523..186e26b 100644 --- a/Scripts/verify_mvp_loading_screen.py +++ b/Scripts/verify_mvp_loading_screen.py @@ -19,7 +19,8 @@ EXPECTED = { "SetActiveScreen(EAgrarianMvpFrontendScreen::Loading)", "Preparing Ground Zero", "Loading terrain, weather, survival state, and server session data.", - "MVP loading placeholder", + "Enter Ground Zero", + "CompleteFrontendFlow", "BarSize.X * 0.62f", ], "AgrarianGamePlayerController.cpp": [ diff --git a/Scripts/verify_startup_credits_sequence.py b/Scripts/verify_startup_credits_sequence.py index 9b2e071..2d548a3 100644 --- a/Scripts/verify_startup_credits_sequence.py +++ b/Scripts/verify_startup_credits_sequence.py @@ -29,6 +29,17 @@ REQUIRED = { "Cherished individuals, Pacificao seed funding, and Lina Family Investment Funds", "SlamSeconds", "DrawCreditIllustration", + "GetCreditsSequenceDurationSeconds", + "Agrarian Startup Credits", + ], + ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.h": [ + "MvpFrontendStartupDelaySeconds = 24.25f", + "ShowMvpFrontend", + ], + ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp": [ + "GetWorldTimerManager().SetTimer", + "ShowMvpFrontend", + "FInputModeGameAndUI", ], ROOT / "Config" / "DefaultGame.ini": [ "ProjectVersion=0.1.N-investor.20260518", diff --git a/Source/AgrarianGame/AgrarianDemoNoticeWidget.cpp b/Source/AgrarianGame/AgrarianDemoNoticeWidget.cpp index e4d6199..f99eea9 100644 --- a/Source/AgrarianGame/AgrarianDemoNoticeWidget.cpp +++ b/Source/AgrarianGame/AgrarianDemoNoticeWidget.cpp @@ -31,6 +31,21 @@ float SmoothStep(float Value) const float ClampedValue = FMath::Clamp(Value, 0.0f, 1.0f); return ClampedValue * ClampedValue * (3.0f - (2.0f * ClampedValue)); } + +float GetCreditsSequenceDurationSeconds() +{ + constexpr float IntroDelay = 0.55f; + constexpr float SlamSeconds = 0.28f; + constexpr float ExitSeconds = 0.48f; + constexpr float GapSeconds = 0.16f; + + float Duration = IntroDelay; + for (const FAgrarianCreditCard& Card : CreditCards) + { + Duration += SlamSeconds + Card.HoldSeconds + ExitSeconds + GapSeconds; + } + return Duration; +} } void UAgrarianDemoNoticeWidget::NativeConstruct() @@ -51,6 +66,26 @@ int32 UAgrarianDemoNoticeWidget::NativePaint( LayerId = Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); const FVector2D Size = AllottedGeometry.GetLocalSize(); + const UWorld* World = GetWorld(); + const float Elapsed = World ? World->GetTimeSeconds() - CreditsStartTimeSeconds : 0.0f; + if (Elapsed <= GetCreditsSequenceDurationSeconds() + 1.0f) + { + FSlateDrawElement::MakeBox( + OutDrawElements, + ++LayerId, + AllottedGeometry.ToPaintGeometry(FVector2f(Size), FSlateLayoutTransform(FVector2f::ZeroVector)), + FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")), + ESlateDrawEffect::None, + FLinearColor(0.006f, 0.008f, 0.007f, 0.98f)); + + const FSlateFontInfo SegmentFont = FCoreStyle::GetDefaultFontStyle("Bold", 22); + const FSlateFontInfo VersionFont = FCoreStyle::GetDefaultFontStyle("Regular", 15); + DrawCenteredText(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Agrarian Startup Credits")), 38.0f, SegmentFont, FLinearColor(0.72f, 0.84f, 0.60f, 1.0f)); + DrawCenteredText(OutDrawElements, LayerId, AllottedGeometry, VersionLabel, 72.0f, VersionFont, FLinearColor(0.58f, 0.64f, 0.54f, 1.0f)); + DrawCinematicCredits(OutDrawElements, LayerId, AllottedGeometry); + return LayerId; + } + const float PanelWidth = FMath::Min(860.0f, FMath::Max(320.0f, Size.X - 96.0f)); const float PanelHeight = 210.0f; const FVector2D PanelPosition((Size.X - PanelWidth) * 0.5f, 42.0f); diff --git a/Source/AgrarianGame/AgrarianGamePlayerController.cpp b/Source/AgrarianGame/AgrarianGamePlayerController.cpp index 6e5294a..52fc2ce 100644 --- a/Source/AgrarianGame/AgrarianGamePlayerController.cpp +++ b/Source/AgrarianGame/AgrarianGamePlayerController.cpp @@ -18,6 +18,7 @@ #include "GameFramework/CharacterMovementComponent.h" #include "InputMappingContext.h" #include "Blueprint/UserWidget.h" +#include "TimerManager.h" #include "AgrarianGame.h" #include "Widgets/Input/SVirtualJoystick.h" @@ -81,16 +82,18 @@ void AAgrarianGamePlayerController::BeginPlay() if (IsLocalPlayerController()) { - if (!MvpFrontendWidgetClass) + if (MvpFrontendStartupDelaySeconds > 0.0f) { - MvpFrontendWidgetClass = UAgrarianMvpFrontendWidget::StaticClass(); + GetWorldTimerManager().SetTimer( + MvpFrontendStartupTimerHandle, + this, + &AAgrarianGamePlayerController::ShowMvpFrontend, + MvpFrontendStartupDelaySeconds, + false); } - - MvpFrontendWidget = CreateWidget(this, MvpFrontendWidgetClass); - if (MvpFrontendWidget) + else { - MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); - MvpFrontendWidget->AddToPlayerScreen(10); + ShowMvpFrontend(); } } @@ -147,6 +150,30 @@ bool AAgrarianGamePlayerController::ShouldUseTouchControls() const return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls; } +void AAgrarianGamePlayerController::ShowMvpFrontend() +{ + if (!IsLocalPlayerController() || MvpFrontendWidget) + { + return; + } + + if (!MvpFrontendWidgetClass) + { + MvpFrontendWidgetClass = UAgrarianMvpFrontendWidget::StaticClass(); + } + + MvpFrontendWidget = CreateWidget(this, MvpFrontendWidgetClass); + if (!MvpFrontendWidget) + { + return; + } + + MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); + MvpFrontendWidget->AddToPlayerScreen(10); + SetInputMode(FInputModeGameAndUI().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock)); + bShowMouseCursor = true; +} + void AAgrarianGamePlayerController::AgrarianGrantItem(FName ItemId, int32 Quantity) { if (ItemId == NAME_None || Quantity <= 0) diff --git a/Source/AgrarianGame/AgrarianGamePlayerController.h b/Source/AgrarianGame/AgrarianGamePlayerController.h index a6777f8..13476a9 100644 --- a/Source/AgrarianGame/AgrarianGamePlayerController.h +++ b/Source/AgrarianGame/AgrarianGamePlayerController.h @@ -44,6 +44,11 @@ protected: UPROPERTY() TObjectPtr MvpFrontendWidget; + UPROPERTY(EditAnywhere, Category = "Agrarian|MVP UI", meta = (ClampMin = "0.0")) + float MvpFrontendStartupDelaySeconds = 24.25f; + + FTimerHandle MvpFrontendStartupTimerHandle; + /** If true, the player will use UMG touch controls even if not playing on mobile platforms */ UPROPERTY(EditAnywhere, Config, Category = "Input|Touch Controls") bool bForceTouchControls = false; @@ -56,6 +61,7 @@ protected: /** Returns true if the player should use UMG touch controls */ bool ShouldUseTouchControls() const; + void ShowMvpFrontend(); public: UFUNCTION(Exec) diff --git a/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp b/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp index 584b987..4529894 100644 --- a/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp +++ b/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp @@ -2,6 +2,7 @@ #include "AgrarianMvpFrontendWidget.h" +#include "GameFramework/PlayerController.h" #include "InputCoreTypes.h" #include "Rendering/DrawElements.h" #include "Styling/CoreStyle.h" @@ -32,7 +33,7 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { - SetActiveScreen(EAgrarianMvpFrontendScreen::JoinServer); + ContinueFromActiveScreen(); return FReply::Handled(); } } @@ -41,13 +42,22 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { - SetActiveScreen(EAgrarianMvpFrontendScreen::Loading); + ContinueFromActiveScreen(); return FReply::Handled(); } if (Key == EKeys::BackSpace || Key == EKeys::Escape) { - SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); + ReturnFromActiveScreen(); + return FReply::Handled(); + } + } + else if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) + { + const FKey Key = InKeyEvent.GetKey(); + if (Key == EKeys::Enter || Key == EKeys::SpaceBar) + { + ContinueFromActiveScreen(); return FReply::Handled(); } } @@ -55,6 +65,89 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, return Super::NativeOnKeyDown(InGeometry, InKeyEvent); } +FReply UAgrarianMvpFrontendWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) +{ + if (InMouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) + { + return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent); + } + + const FVector2D LocalMousePosition = InGeometry.AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition()); + float Scale = 1.0f; + FVector2D PanelPosition = FVector2D::ZeroVector; + FVector2D PanelSize = FVector2D::ZeroVector; + if (!GetPanelLayout(InGeometry.GetLocalSize(), Scale, PanelPosition, PanelSize)) + { + return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent); + } + + const float ContentX = PanelPosition.X + (44.0f * Scale); + const float ContentWidth = PanelSize.X - (88.0f * Scale); + + if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection) + { + const FVector2D CardSize((ContentWidth - (24.0f * Scale)) * 0.5f, 206.0f * Scale); + const FVector2D MaleCardPosition(ContentX, PanelPosition.Y + (142.0f * Scale)); + const FVector2D FemaleCardPosition(ContentX + CardSize.X + (24.0f * Scale), MaleCardPosition.Y); + const FVector2D ButtonPosition(ContentX, PanelPosition.Y + PanelSize.Y - (88.0f * Scale)); + const FVector2D ButtonSize(FMath::Min(ContentWidth, 280.0f * Scale), 52.0f * Scale); + + if (IsPointInside(LocalMousePosition, MaleCardPosition, CardSize)) + { + SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); + return FReply::Handled(); + } + + if (IsPointInside(LocalMousePosition, FemaleCardPosition, CardSize)) + { + SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); + return FReply::Handled(); + } + + if (IsPointInside(LocalMousePosition, ButtonPosition, ButtonSize)) + { + ContinueFromActiveScreen(); + return FReply::Handled(); + } + } + else if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) + { + const float JoinContentX = PanelPosition.X + (48.0f * Scale); + const float JoinContentWidth = PanelSize.X - (96.0f * Scale); + const FVector2D ButtonPosition(JoinContentX, PanelPosition.Y + (300.0f * Scale)); + const FVector2D ButtonSize(FMath::Min(JoinContentWidth, 330.0f * Scale), 58.0f * Scale); + const FVector2D BackButtonPosition(ButtonPosition.X + ButtonSize.X + (16.0f * Scale), ButtonPosition.Y); + const FVector2D BackButtonSize(FMath::Min(JoinContentWidth - ButtonSize.X - (16.0f * Scale), 180.0f * Scale), ButtonSize.Y); + + if (IsPointInside(LocalMousePosition, ButtonPosition, ButtonSize)) + { + ContinueFromActiveScreen(); + return FReply::Handled(); + } + + if (BackButtonSize.X > 96.0f && IsPointInside(LocalMousePosition, BackButtonPosition, BackButtonSize)) + { + ReturnFromActiveScreen(); + return FReply::Handled(); + } + } + else if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) + { + const float LoadingContentX = PanelPosition.X + (48.0f * Scale); + const float LoadingContentWidth = PanelSize.X - (96.0f * Scale); + const FVector2D ButtonPosition(LoadingContentX, PanelPosition.Y + (318.0f * Scale)); + const FVector2D ButtonSize(FMath::Min(LoadingContentWidth, 310.0f * Scale), 56.0f * Scale); + + if (IsPointInside(LocalMousePosition, ButtonPosition, ButtonSize)) + { + ContinueFromActiveScreen(); + return FReply::Handled(); + } + } + + return FReply::Handled(); +} + void UAgrarianMvpFrontendWidget::SetActiveScreen(EAgrarianMvpFrontendScreen NewScreen) { ActiveScreen = NewScreen; @@ -91,13 +184,10 @@ int32 UAgrarianMvpFrontendWidget::NativePaint( LayerId = Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); const FVector2D Size = AllottedGeometry.GetLocalSize(); - const float Scale = FMath::Clamp(UiScale, 0.75f, 1.5f); - constexpr float MinimumPanelMargin = 24.0f; - constexpr float PreferredPanelWidth = 780.0f; - constexpr float PreferredPanelHeight = 430.0f; - const FVector2D AvailablePanelSize( - FMath::Max(320.0f, Size.X - (MinimumPanelMargin * 2.0f)), - FMath::Max(240.0f, Size.Y - (MinimumPanelMargin * 2.0f))); + float Scale = 1.0f; + FVector2D PanelPosition = FVector2D::ZeroVector; + FVector2D PanelSize = FVector2D::ZeroVector; + GetPanelLayout(Size, Scale, PanelPosition, PanelSize); const FLinearColor BackdropColor = bUseHighContrast ? FLinearColor(0.0f, 0.0f, 0.0f, 0.96f) : FLinearColor(0.015f, 0.018f, 0.014f, 0.92f); 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); @@ -110,13 +200,6 @@ int32 UAgrarianMvpFrontendWidget::NativePaint( ESlateDrawEffect::None, BackdropColor); - const FVector2D PanelSize( - FMath::Min(AvailablePanelSize.X, PreferredPanelWidth * Scale), - FMath::Min(AvailablePanelSize.Y, PreferredPanelHeight * Scale)); - const FVector2D PanelPosition( - (Size.X - PanelSize.X) * 0.5f, - (Size.Y - PanelSize.Y) * 0.5f); - FSlateDrawElement::MakeBox( OutDrawElements, ++LayerId, @@ -153,6 +236,73 @@ int32 UAgrarianMvpFrontendWidget::NativePaint( return LayerId; } +bool UAgrarianMvpFrontendWidget::GetPanelLayout(const FVector2D& WidgetSize, float& OutScale, FVector2D& OutPanelPosition, FVector2D& OutPanelSize) const +{ + OutScale = FMath::Clamp(UiScale, 0.75f, 1.5f); + constexpr float MinimumPanelMargin = 24.0f; + constexpr float PreferredPanelWidth = 780.0f; + constexpr float PreferredPanelHeight = 430.0f; + const FVector2D AvailablePanelSize( + FMath::Max(320.0f, WidgetSize.X - (MinimumPanelMargin * 2.0f)), + FMath::Max(240.0f, WidgetSize.Y - (MinimumPanelMargin * 2.0f))); + + OutPanelSize = FVector2D( + FMath::Min(AvailablePanelSize.X, PreferredPanelWidth * OutScale), + FMath::Min(AvailablePanelSize.Y, PreferredPanelHeight * OutScale)); + OutPanelPosition = FVector2D( + (WidgetSize.X - OutPanelSize.X) * 0.5f, + (WidgetSize.Y - OutPanelSize.Y) * 0.5f); + + return WidgetSize.X > 0.0f && WidgetSize.Y > 0.0f; +} + +bool UAgrarianMvpFrontendWidget::IsPointInside(const FVector2D& Point, const FVector2D& Position, const FVector2D& Size) const +{ + return Point.X >= Position.X + && Point.Y >= Position.Y + && Point.X <= Position.X + Size.X + && Point.Y <= Position.Y + Size.Y; +} + +void UAgrarianMvpFrontendWidget::ContinueFromActiveScreen() +{ + if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection) + { + SetActiveScreen(EAgrarianMvpFrontendScreen::JoinServer); + return; + } + + if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) + { + SetActiveScreen(EAgrarianMvpFrontendScreen::Loading); + return; + } + + if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) + { + CompleteFrontendFlow(); + } +} + +void UAgrarianMvpFrontendWidget::ReturnFromActiveScreen() +{ + if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) + { + SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); + } +} + +void UAgrarianMvpFrontendWidget::CompleteFrontendFlow() +{ + if (APlayerController* PlayerController = GetOwningPlayer()) + { + PlayerController->SetInputMode(FInputModeGameOnly()); + PlayerController->bShowMouseCursor = false; + } + + RemoveFromParent(); +} + void UAgrarianMvpFrontendWidget::DrawMainMenu( FSlateWindowElementList& OutDrawElements, int32& LayerId, @@ -200,8 +350,8 @@ void UAgrarianMvpFrontendWidget::DrawCharacterSelection( const FSlateFontInfo CardTitleFont = FCoreStyle::GetDefaultFontStyle("Bold", FMath::RoundToInt(22.0f * Scale)); const FSlateFontInfo LabelFont = FCoreStyle::GetDefaultFontStyle("Regular", FMath::RoundToInt(15.0f * Scale)); - DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Choose your first settler")), FVector2D(ContentX, PanelPosition.Y + (34.0f * Scale)), ContentWidth, TitleFont, FLinearColor(0.92f, 0.98f, 0.84f, 1.0f)); - DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("MVP placeholder landing page before entering Ground Zero.")), FVector2D(ContentX + (2.0f * Scale), PanelPosition.Y + (82.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.72f, 0.80f, 0.68f, 1.0f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Choose your first pioneer")), FVector2D(ContentX, PanelPosition.Y + (34.0f * Scale)), ContentWidth, TitleFont, FLinearColor(0.92f, 0.98f, 0.84f, 1.0f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Select the person who will step into Ground Zero.")), FVector2D(ContentX + (2.0f * Scale), PanelPosition.Y + (82.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.72f, 0.80f, 0.68f, 1.0f)); const FVector2D CardSize((ContentWidth - (24.0f * Scale)) * 0.5f, 206.0f * Scale); const FVector2D MaleCardPosition(ContentX, PanelPosition.Y + (142.0f * Scale)); @@ -254,7 +404,18 @@ void UAgrarianMvpFrontendWidget::DrawCharacterSelection( DrawCharacterCard(MaleCardPosition, FText::FromString(TEXT("Young adult male")), FText::FromString(TEXT("Average proportions, survival baseline, placeholder visual.")), FLinearColor(0.36f, 0.58f, 0.78f, 1.0f), SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultMale); DrawCharacterCard(FemaleCardPosition, FText::FromString(TEXT("Young adult female")), FText::FromString(TEXT("Average proportions, survival baseline, placeholder visual.")), FLinearColor(0.56f, 0.68f, 0.46f, 1.0f), SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale); - DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("Selected: {0}. Use Left/Right or A/D to choose; both choices enter the same MVP survival baseline.")), GetSelectedCharacterLabel()), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (46.0f * Scale)), ContentWidth, LabelFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f)); + const FVector2D ButtonPosition(ContentX, PanelPosition.Y + PanelSize.Y - (88.0f * Scale)); + const FVector2D ButtonSize(FMath::Min(ContentWidth, 280.0f * Scale), 52.0f * Scale); + FSlateDrawElement::MakeBox( + OutDrawElements, + ++LayerId, + AllottedGeometry.ToPaintGeometry(FVector2f(ButtonSize), FSlateLayoutTransform(FVector2f(ButtonPosition))), + FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")), + ESlateDrawEffect::None, + FLinearColor(0.35f, 0.58f, 0.30f, 0.95f)); + + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Continue")), ButtonPosition + FVector2D(22.0f * Scale, 10.0f * Scale), ButtonSize.X - (44.0f * Scale), CardTitleFont, FLinearColor(0.96f, 1.0f, 0.90f, 1.0f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("Selected {0}: {1}. Click a card or use Left/Right, then continue.")), GetSelectedRoleLabel(), GetSelectedCharacterLabel()), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (26.0f * Scale)), ContentWidth, LabelFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f)); } void UAgrarianMvpFrontendWidget::DrawJoinServer( @@ -273,7 +434,7 @@ void UAgrarianMvpFrontendWidget::DrawJoinServer( const FSlateFontInfo HintFont = FCoreStyle::GetDefaultFontStyle("Regular", FMath::RoundToInt(15.0f * Scale)); DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Join MVP server")), FVector2D(ContentX, PanelPosition.Y + (44.0f * Scale)), ContentWidth, TitleFont, FLinearColor(0.92f, 0.98f, 0.84f, 1.0f)); - DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("Character: {0}")), GetSelectedCharacterLabel()), FVector2D(ContentX + (2.0f * Scale), PanelPosition.Y + (94.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.72f, 0.80f, 0.68f, 1.0f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("{0}: {1}")), GetSelectedRoleLabel(), GetSelectedCharacterLabel()), FVector2D(ContentX + (2.0f * Scale), PanelPosition.Y + (94.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.72f, 0.80f, 0.68f, 1.0f)); const FVector2D AddressPanelPosition(ContentX, PanelPosition.Y + (164.0f * Scale)); const FVector2D AddressPanelSize(ContentWidth, 96.0f * Scale); @@ -299,7 +460,20 @@ void UAgrarianMvpFrontendWidget::DrawJoinServer( FLinearColor(0.35f, 0.58f, 0.30f, 0.95f)); DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Continue to loading")), ButtonPosition + FVector2D(22.0f * Scale, 12.0f * Scale), ButtonSize.X - (44.0f * Scale), AddressFont, FLinearColor(0.96f, 1.0f, 0.90f, 1.0f)); - DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Press Backspace or Escape to return to character selection.")), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (46.0f * Scale)), ContentWidth, HintFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f)); + const FVector2D BackButtonPosition(ButtonPosition.X + ButtonSize.X + (16.0f * Scale), ButtonPosition.Y); + const FVector2D BackButtonSize(FMath::Min(ContentWidth - ButtonSize.X - (16.0f * Scale), 180.0f * Scale), ButtonSize.Y); + if (BackButtonSize.X > 96.0f) + { + FSlateDrawElement::MakeBox( + OutDrawElements, + ++LayerId, + AllottedGeometry.ToPaintGeometry(FVector2f(BackButtonSize), FSlateLayoutTransform(FVector2f(BackButtonPosition))), + FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")), + ESlateDrawEffect::None, + FLinearColor(0.12f, 0.15f, 0.11f, 0.95f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Back")), BackButtonPosition + FVector2D(22.0f * Scale, 12.0f * Scale), BackButtonSize.X - (44.0f * Scale), AddressFont, FLinearColor(0.82f, 0.88f, 0.76f, 1.0f)); + } + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Click continue, press Enter, or press Backspace/Escape to revise your pioneer.")), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (46.0f * Scale)), ContentWidth, HintFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f)); } void UAgrarianMvpFrontendWidget::DrawLoading( @@ -337,8 +511,19 @@ void UAgrarianMvpFrontendWidget::DrawLoading( ESlateDrawEffect::None, FLinearColor(0.45f, 0.72f, 0.40f, 1.0f)); - DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("Character: {0} | Server: {1}")), GetSelectedCharacterLabel(), JoinServerAddress), FVector2D(ContentX, PanelPosition.Y + (270.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.78f, 0.84f, 0.72f, 1.0f)); - DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("MVP loading placeholder; final connection and map handoff will attach here.")), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (46.0f * Scale)), ContentWidth, HintFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("{0}: {1} | Server: {2}")), GetSelectedRoleLabel(), GetSelectedCharacterLabel(), JoinServerAddress), FVector2D(ContentX, PanelPosition.Y + (270.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.78f, 0.84f, 0.72f, 1.0f)); + + const FVector2D ButtonPosition(ContentX, PanelPosition.Y + (318.0f * Scale)); + const FVector2D ButtonSize(FMath::Min(ContentWidth, 310.0f * Scale), 56.0f * Scale); + FSlateDrawElement::MakeBox( + OutDrawElements, + ++LayerId, + AllottedGeometry.ToPaintGeometry(FVector2f(ButtonSize), FSlateLayoutTransform(FVector2f(ButtonPosition))), + FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")), + ESlateDrawEffect::None, + FLinearColor(0.35f, 0.58f, 0.30f, 0.95f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Enter Ground Zero")), ButtonPosition + FVector2D(22.0f * Scale, 11.0f * Scale), ButtonSize.X - (44.0f * Scale), BodyFont, FLinearColor(0.96f, 1.0f, 0.90f, 1.0f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Click or press Enter to close the MVP menu and begin testing.")), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (46.0f * Scale)), ContentWidth, HintFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f)); } FText UAgrarianMvpFrontendWidget::GetSelectedCharacterLabel() const @@ -348,6 +533,11 @@ FText UAgrarianMvpFrontendWidget::GetSelectedCharacterLabel() const : FText::FromString(TEXT("young adult male")); } +FText UAgrarianMvpFrontendWidget::GetSelectedRoleLabel() const +{ + return FText::FromString(TEXT("Pioneer")); +} + void UAgrarianMvpFrontendWidget::DrawTextAt( FSlateWindowElementList& OutDrawElements, int32& LayerId, diff --git a/Source/AgrarianGame/AgrarianMvpFrontendWidget.h b/Source/AgrarianGame/AgrarianMvpFrontendWidget.h index 196c4fc..606af6d 100644 --- a/Source/AgrarianGame/AgrarianMvpFrontendWidget.h +++ b/Source/AgrarianGame/AgrarianMvpFrontendWidget.h @@ -68,6 +68,7 @@ protected: virtual void NativeConstruct() override; virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override; + virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; virtual int32 NativePaint( const FPaintArgs& Args, @@ -79,6 +80,12 @@ protected: bool bParentEnabled) const override; private: + bool GetPanelLayout(const FVector2D& WidgetSize, float& OutScale, FVector2D& OutPanelPosition, FVector2D& OutPanelSize) const; + bool IsPointInside(const FVector2D& Point, const FVector2D& Position, const FVector2D& Size) const; + void ContinueFromActiveScreen(); + void ReturnFromActiveScreen(); + void CompleteFrontendFlow(); + void DrawMainMenu( FSlateWindowElementList& OutDrawElements, int32& LayerId, @@ -112,6 +119,7 @@ private: float Scale) const; FText GetSelectedCharacterLabel() const; + FText GetSelectedRoleLabel() const; void DrawTextAt( FSlateWindowElementList& OutDrawElements,