Setup phone UI
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -351,6 +351,7 @@ Phase estimates are rough and assume one engineer. Adjust as we go.
|
||||
|
||||
### Phase 8 — Phone + forum UI + battery + livestream (3–4 weeks)
|
||||
|
||||
- **Phone UI shell — landed early (pulled forward from this phase).** CommonUI nested-stack shell under `UI/Phone/`: `UPhoneScreenWidget` (activatable pushed onto the GameLayout `WidgetStack`) owns an inner `AppStack` (`UCommonActivatableWidgetStack`) with the home screen at its base; `OpenApp` pushes an app, the physical `HomeButton`/`GoHome` clears back to home, and CommonUI back navigation pops app→home→close-phone for free. `UPhoneAppWidget` is the abstract base for every app; `UPhoneHomeScreenWidget` (a `PhoneAppWidget`) builds an icon grid from a data-driven `AppEntries` array (`FPhoneAppEntry` = name/icon/`TSubclassOf<UPhoneAppWidget>`, §17.4) into a `BindWidget` `AppContainer`, spawning one `UPhoneAppIconWidget` each and routing taps back to `OpenApp` via `OnAppSelected`. `UGameLayoutWidget::OpenPhone()` pushes the shell. **Open path is temporary:** a dev `PhoneAction` input on `ANakedDesireCharacter` (`OnPhonePress`) calls `OpenPhone()` — replace with phone-slot interaction + `BlockPhoneUse`/battery gating when the systems below land. **BP to author in-editor:** `WBP_PhoneScreen` (binds `AppStack`, `HomeButton`; set `HomeScreenClass`), `WBP_PhoneHome` (binds `AppContainer`; set `AppIconWidgetClass` + fill `AppEntries`), `WBP_PhoneAppIcon` (binds `IconButton`; optional `IconImage`/`NameText`), placeholder `WBP_App_Camera`/`Gallery`/`Forum` (each a `UPhoneAppWidget`); assign `PhoneScreenWidgetClass` on the GameLayout BP and the `PhoneAction` `UInputAction` + mapping on the player BP. App **contents** (capture, posting, etc.) still pending below.
|
||||
- Phone as an `AItemActor` (Phase 2 base). Usable from the dedicated **phone slot** (§6.5 / §27); placed-in-world streaming exception still applies (§9.1.1).
|
||||
- `PhoneSubsystem` (§17.1): tickable; owns battery %, active app, livestream session lifecycle, charger interaction.
|
||||
- **Battery (§9.8):**
|
||||
|
||||
@@ -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