Setup phone UI

This commit is contained in:
2026-06-02 00:06:13 +03:00
parent c529544bc7
commit ff62b06802
20 changed files with 350 additions and 5 deletions
@@ -88,6 +88,7 @@ void ANakedDesireCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInp
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Completed, this, &ANakedDesireCharacter::OnCrouchToggle);
EnhancedInputComponent->BindAction(EquipmentAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnEquipmentPress);
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnInteractPress);
EnhancedInputComponent->BindAction(PhoneAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnPhonePress);
}
}
@@ -264,6 +265,11 @@ void ANakedDesireCharacter::OnInteractPress(const FInputActionValue& Value)
InteractionComponent->Interact();
}
void ANakedDesireCharacter::OnPhonePress(const FInputActionValue& Value)
{
HUD->GetGameLayoutWidget()->OpenPhone();
}
void ANakedDesireCharacter::NotifyNoticed(ANPCAIController* NPCController)
{
OnNoticed.Broadcast(NPCController);
@@ -57,6 +57,10 @@ public:
UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputAction* InteractAction;
// Temporary dev entry to open the phone; replace with phone-slot interaction in Phase 8 (GDD §9 / §6.5).
UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputAction* PhoneAction;
// Clothing slot meshes are spawned per equipped slot at runtime by ClothingVisualsComponent.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Clothing")
UClothingVisualsComponent* ClothingVisualsComponent;
@@ -140,6 +144,7 @@ private:
void OnCrouchToggle(const FInputActionValue& Value);
void OnEquipmentPress(const FInputActionValue& Value);
void OnInteractPress(const FInputActionValue& Value);
void OnPhonePress(const FInputActionValue& Value);
bool CheckSight(const FVector& StartLocation, const FVector& EndLocation, FHitResult& HitResult, const AActor* IgnoreActor);
@@ -2,6 +2,7 @@
#include "Widgets/CommonActivatableWidgetContainer.h"
#include "Inventory/InventoryScreenWidget.h"
#include "Inventory/Wardrobe/WardrobeScreenWidget.h"
#include "Phone/PhoneScreenWidget.h"
void UGameLayoutWidget::OpenInventory()
{
@@ -12,3 +13,8 @@ void UGameLayoutWidget::OpenWardrobe()
{
WardrobeScreenWidget = WidgetStack->AddWidget<UWardrobeScreenWidget>(WardrobeScreenWidgetClass);
}
void UGameLayoutWidget::OpenPhone()
{
PhoneScreenWidget = WidgetStack->AddWidget<UPhoneScreenWidget>(PhoneScreenWidgetClass);
}
+9 -1
View File
@@ -6,6 +6,7 @@
class UWardrobeScreenWidget;
class UInventoryScreenWidget;
class UPhoneScreenWidget;
class UHUDWidget;
class UCommonActivatableWidgetStack;
@@ -31,8 +32,15 @@ class NAKEDDESIRE_API UGameLayoutWidget : public UCommonUserWidget
UPROPERTY()
TObjectPtr<UWardrobeScreenWidget> WardrobeScreenWidget;
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<UPhoneScreenWidget> PhoneScreenWidgetClass;
UPROPERTY()
TObjectPtr<UPhoneScreenWidget> PhoneScreenWidget;
public:
void OpenInventory();
void OpenWardrobe();
void OpenPhone();
};
@@ -0,0 +1,30 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "PhoneAppIconWidget.h"
#include "Components/Button.h"
#include "Components/Image.h"
#include "CommonTextBlock.h"
void UPhoneAppIconWidget::Init(const FText& InName, UTexture2D* InIcon, TSubclassOf<UPhoneAppWidget> InAppClass)
{
AppClass = InAppClass;
if (NameText)
{
NameText->SetText(InName);
}
if (IconImage)
{
IconImage->SetBrushFromTexture(InIcon);
IconImage->SetVisibility(InIcon ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
}
IconButton->OnClicked.AddUniqueDynamic(this, &UPhoneAppIconWidget::HandleClicked);
}
void UPhoneAppIconWidget::HandleClicked()
{
OnClicked.ExecuteIfBound(AppClass);
}
@@ -0,0 +1,43 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "PhoneAppIconWidget.generated.h"
class UButton;
class UImage;
class UCommonTextBlock;
class UPhoneAppWidget;
class UTexture2D;
// One launcher icon on the phone home screen. Built at runtime by UPhoneHomeScreenWidget from its
// AppEntries array; broadcasts the app class to launch when tapped.
UCLASS(Abstract)
class NAKEDDESIRE_API UPhoneAppIconWidget : public UCommonUserWidget
{
GENERATED_BODY()
public:
void Init(const FText& InName, UTexture2D* InIcon, TSubclassOf<UPhoneAppWidget> InAppClass);
DECLARE_DELEGATE_OneParam(FOnAppIconClicked, TSubclassOf<UPhoneAppWidget>);
FOnAppIconClicked OnClicked;
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> IconButton;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UImage> IconImage;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> NameText;
UPROPERTY()
TSubclassOf<UPhoneAppWidget> AppClass;
UFUNCTION()
void HandleClicked();
};
@@ -0,0 +1,18 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonActivatableWidget.h"
#include "PhoneAppWidget.generated.h"
// Base class for every phone app (GDD §9: camera, gallery, forum, bank, livestream, maps, health tracker).
// Apps are activatables pushed onto the phone shell's inner app stack (UPhoneScreenWidget::AppStack).
// CommonUI back navigation pops the active app back to the home screen for free; the phone's physical
// Home button (UPhoneScreenWidget::GoHome) is the explicit equivalent.
// Per-app logic (capture, posting, etc.) lands with the Phase 8 phone systems; this is the shared shell only.
UCLASS(Abstract)
class NAKEDDESIRE_API UPhoneAppWidget : public UCommonActivatableWidget
{
GENERATED_BODY()
};
@@ -0,0 +1,36 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "PhoneHomeScreenWidget.h"
#include "PhoneAppIconWidget.h"
#include "Components/PanelWidget.h"
void UPhoneHomeScreenWidget::NativeConstruct()
{
Super::NativeConstruct();
if (!AppContainer || !AppIconWidgetClass)
{
return;
}
AppContainer->ClearChildren();
for (const FPhoneAppEntry& Entry : AppEntries)
{
if (!Entry.AppClass)
{
continue;
}
UPhoneAppIconWidget* Icon = CreateWidget<UPhoneAppIconWidget>(this, AppIconWidgetClass);
Icon->Init(Entry.AppName, Entry.AppIcon, Entry.AppClass);
Icon->OnClicked.BindUObject(this, &UPhoneHomeScreenWidget::HandleAppIconClicked);
AppContainer->AddChild(Icon);
}
}
void UPhoneHomeScreenWidget::HandleAppIconClicked(TSubclassOf<UPhoneAppWidget> AppClass)
{
OnAppSelected.ExecuteIfBound(AppClass);
}
@@ -0,0 +1,57 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "PhoneAppWidget.h"
#include "PhoneHomeScreenWidget.generated.h"
class UPanelWidget;
class UPhoneAppIconWidget;
class UTexture2D;
// One launchable app on the home screen. Designers fill the home screen's AppEntries array in the BP,
// keeping the app roster data-driven (GDD §17.4) rather than hardcoded.
USTRUCT(BlueprintType)
struct FPhoneAppEntry
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, Category = "Phone App")
FText AppName;
UPROPERTY(EditDefaultsOnly, Category = "Phone App")
TObjectPtr<UTexture2D> AppIcon;
UPROPERTY(EditDefaultsOnly, Category = "Phone App")
TSubclassOf<UPhoneAppWidget> AppClass;
};
// The phone home screen — the base of the app stack. Builds a grid of app icons from AppEntries and
// asks the owning phone shell to launch the chosen app via OnAppSelected.
UCLASS(Abstract)
class NAKEDDESIRE_API UPhoneHomeScreenWidget : public UPhoneAppWidget
{
GENERATED_BODY()
public:
DECLARE_DELEGATE_OneParam(FOnAppSelected, TSubclassOf<UPhoneAppWidget>);
FOnAppSelected OnAppSelected;
protected:
virtual void NativeConstruct() override;
private:
// Container the app icons are spawned into. Use a flow container (WrapBox / Horizontal / VerticalBox)
// authored in the BP — plain AddChild on a UniformGridPanel stacks every child in cell (0,0).
UPROPERTY(meta = (BindWidget))
TObjectPtr<UPanelWidget> AppContainer;
UPROPERTY(EditDefaultsOnly, Category = "Phone")
TSubclassOf<UPhoneAppIconWidget> AppIconWidgetClass;
UPROPERTY(EditDefaultsOnly, Category = "Phone")
TArray<FPhoneAppEntry> AppEntries;
void HandleAppIconClicked(TSubclassOf<UPhoneAppWidget> AppClass);
};
@@ -0,0 +1,67 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "PhoneScreenWidget.h"
#include "PhoneHomeScreenWidget.h"
#include "Components/Button.h"
#include "Widgets/CommonActivatableWidgetContainer.h"
void UPhoneScreenWidget::NativeOnActivated()
{
Super::NativeOnActivated();
HomeButton->OnClicked.AddUniqueDynamic(this, &UPhoneScreenWidget::GoHome);
// Open on the home screen every time the phone is brought up.
if (AppStack)
{
AppStack->ClearWidgets();
PushHomeScreen();
}
}
UWidget* UPhoneScreenWidget::NativeGetDesiredFocusTarget() const
{
return HomeButton;
}
void UPhoneScreenWidget::OpenApp(TSubclassOf<UPhoneAppWidget> AppClass)
{
if (AppStack && AppClass)
{
AppStack->AddWidget<UPhoneAppWidget>(AppClass);
}
}
UPhoneHomeScreenWidget* UPhoneScreenWidget::PushHomeScreen()
{
if (!AppStack || !HomeScreenClass)
{
return nullptr;
}
HomeScreen = AppStack->AddWidget<UPhoneHomeScreenWidget>(HomeScreenClass);
if (HomeScreen)
{
HomeScreen->OnAppSelected.BindUObject(this, &UPhoneScreenWidget::OpenApp);
}
return HomeScreen;
}
void UPhoneScreenWidget::GoHome()
{
if (!AppStack)
{
return;
}
// Already on the home screen — nothing to do.
if (HomeScreen && AppStack->GetActiveWidget() == HomeScreen)
{
DeactivateWidget();
}
// Drop every app and re-seat the home screen at the base of the stack.
AppStack->ClearWidgets();
PushHomeScreen();
}
@@ -0,0 +1,50 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonActivatableWidget.h"
#include "PhoneScreenWidget.generated.h"
class UButton;
class UCommonActivatableWidgetStack;
class UPhoneAppWidget;
class UPhoneHomeScreenWidget;
// The phone shell — one activatable pushed onto the GameLayout WidgetStack. It owns an inner app stack:
// the home screen sits at the base, apps push on top. CommonUI back navigation pops the active app to
// home, then closes the phone at home; the physical Home button (GoHome) is the explicit equivalent.
// Battery / BlockPhoneUse gating and the real apps land with the Phase 8 phone systems (GDD §9).
UCLASS(Abstract)
class NAKEDDESIRE_API UPhoneScreenWidget : public UCommonActivatableWidget
{
GENERATED_BODY()
public:
// Push an app onto the stack, on top of the home screen.
void OpenApp(TSubclassOf<UPhoneAppWidget> AppClass);
protected:
virtual void NativeOnActivated() override;
virtual UWidget* NativeGetDesiredFocusTarget() const override;
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> AppStack;
// The phone's physical home button — clears the app stack back to the home screen.
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> HomeButton;
UPROPERTY(EditDefaultsOnly, Category = "Phone")
TSubclassOf<UPhoneHomeScreenWidget> HomeScreenClass;
// The live home screen at the base of the stack, tracked so GoHome can skip work when it's already on top.
UPROPERTY(Transient)
TObjectPtr<UPhoneHomeScreenWidget> HomeScreen;
UPhoneHomeScreenWidget* PushHomeScreen();
UFUNCTION()
void GoHome();
};