Setup phone UI
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
Reference in New Issue
Block a user