From dad848cfbc5246f16ee440f16a16387173686e34 Mon Sep 17 00:00:00 2001 From: koritsa Date: Tue, 2 Jun 2026 00:06:13 +0300 Subject: [PATCH] Setup phone UI --- Content/Blueprints/Player/BP_Player.uasset | 4 +- Content/UI/Phone/WBP_App_Camera.uasset | 3 + Content/UI/Phone/WBP_App_Forum.uasset | 3 + Content/UI/Phone/WBP_App_Gallery.uasset | 3 + Content/UI/Phone/WBP_PhoneAppIcon.uasset | 3 + Content/UI/Phone/WBP_PhoneHome.uasset | 3 + Content/UI/Phone/WBP_PhoneScreen.uasset | 3 + Content/UI/WBP_GameLayout.uasset | 4 +- PLAN.md | 1 + .../Player/NakedDesireCharacter.cpp | 6 ++ .../NakedDesire/Player/NakedDesireCharacter.h | 5 ++ Source/NakedDesire/UI/GameLayoutWidget.cpp | 6 ++ Source/NakedDesire/UI/GameLayoutWidget.h | 10 ++- .../UI/Phone/PhoneAppIconWidget.cpp | 30 +++++++++ .../NakedDesire/UI/Phone/PhoneAppIconWidget.h | 43 ++++++++++++ Source/NakedDesire/UI/Phone/PhoneAppWidget.h | 18 +++++ .../UI/Phone/PhoneHomeScreenWidget.cpp | 36 ++++++++++ .../UI/Phone/PhoneHomeScreenWidget.h | 57 ++++++++++++++++ .../UI/Phone/PhoneScreenWidget.cpp | 67 +++++++++++++++++++ .../NakedDesire/UI/Phone/PhoneScreenWidget.h | 50 ++++++++++++++ 20 files changed, 350 insertions(+), 5 deletions(-) create mode 100644 Content/UI/Phone/WBP_App_Camera.uasset create mode 100644 Content/UI/Phone/WBP_App_Forum.uasset create mode 100644 Content/UI/Phone/WBP_App_Gallery.uasset create mode 100644 Content/UI/Phone/WBP_PhoneAppIcon.uasset create mode 100644 Content/UI/Phone/WBP_PhoneHome.uasset create mode 100644 Content/UI/Phone/WBP_PhoneScreen.uasset create mode 100644 Source/NakedDesire/UI/Phone/PhoneAppIconWidget.cpp create mode 100644 Source/NakedDesire/UI/Phone/PhoneAppIconWidget.h create mode 100644 Source/NakedDesire/UI/Phone/PhoneAppWidget.h create mode 100644 Source/NakedDesire/UI/Phone/PhoneHomeScreenWidget.cpp create mode 100644 Source/NakedDesire/UI/Phone/PhoneHomeScreenWidget.h create mode 100644 Source/NakedDesire/UI/Phone/PhoneScreenWidget.cpp create mode 100644 Source/NakedDesire/UI/Phone/PhoneScreenWidget.h diff --git a/Content/Blueprints/Player/BP_Player.uasset b/Content/Blueprints/Player/BP_Player.uasset index 20de698e..182f972e 100644 --- a/Content/Blueprints/Player/BP_Player.uasset +++ b/Content/Blueprints/Player/BP_Player.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e933f3784a16d6dc4889a4114aa73acac750b7da5d4a651ddb80cfd313a08f2d -size 71937 +oid sha256:0bb35097f2f39926da90eb5e9ba08cddc6d5f60c929eb5cf4d23242d1b41d665 +size 71867 diff --git a/Content/UI/Phone/WBP_App_Camera.uasset b/Content/UI/Phone/WBP_App_Camera.uasset new file mode 100644 index 00000000..ce1b10d0 --- /dev/null +++ b/Content/UI/Phone/WBP_App_Camera.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:648b797b1754652aca4b2bbc11dca2a9b5e4a8a428b1d73ebe69c009b4c0487c +size 24056 diff --git a/Content/UI/Phone/WBP_App_Forum.uasset b/Content/UI/Phone/WBP_App_Forum.uasset new file mode 100644 index 00000000..a6f9417d --- /dev/null +++ b/Content/UI/Phone/WBP_App_Forum.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1c55b53d34c9ad1a10531f2dcbb20d3f2241e3e307f6aea674f65305904f2d6 +size 23354 diff --git a/Content/UI/Phone/WBP_App_Gallery.uasset b/Content/UI/Phone/WBP_App_Gallery.uasset new file mode 100644 index 00000000..4f6f4b03 --- /dev/null +++ b/Content/UI/Phone/WBP_App_Gallery.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b4c7fbb7043f04f3a8b811b26f80dbfd5d16d148f8edfad51e3ebd96071b22a +size 23484 diff --git a/Content/UI/Phone/WBP_PhoneAppIcon.uasset b/Content/UI/Phone/WBP_PhoneAppIcon.uasset new file mode 100644 index 00000000..3ef6fd23 --- /dev/null +++ b/Content/UI/Phone/WBP_PhoneAppIcon.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:925bcebad19eddef69b4e1266ecb47df827d79641e09d2cc273a4b4b39dfdcdb +size 29656 diff --git a/Content/UI/Phone/WBP_PhoneHome.uasset b/Content/UI/Phone/WBP_PhoneHome.uasset new file mode 100644 index 00000000..fa19f03a --- /dev/null +++ b/Content/UI/Phone/WBP_PhoneHome.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b8b23b9ad79325f5d674ab84c49c8785806fdd5a5dba456feb92566e6324cd +size 26874 diff --git a/Content/UI/Phone/WBP_PhoneScreen.uasset b/Content/UI/Phone/WBP_PhoneScreen.uasset new file mode 100644 index 00000000..2c7640ae --- /dev/null +++ b/Content/UI/Phone/WBP_PhoneScreen.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52313ebc59e580449117c9df83a5e128c9439a3160031e417e19dd14a1bd105c +size 31368 diff --git a/Content/UI/WBP_GameLayout.uasset b/Content/UI/WBP_GameLayout.uasset index 4399f872..340eec4f 100644 --- a/Content/UI/WBP_GameLayout.uasset +++ b/Content/UI/WBP_GameLayout.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80dbeda35c014db49210c5b59dcbdbde589b2458eeba1e7691c681f6630c50bf -size 28220 +oid sha256:424276b717d29192ff79d50969e3449c212f2f50a9bd9e93615688ea20e32ad3 +size 26811 diff --git a/PLAN.md b/PLAN.md index 37ef9e21..925c5f49 100644 --- a/PLAN.md +++ b/PLAN.md @@ -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`, §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):** diff --git a/Source/NakedDesire/Player/NakedDesireCharacter.cpp b/Source/NakedDesire/Player/NakedDesireCharacter.cpp index ac3f33cb..51ded976 100644 --- a/Source/NakedDesire/Player/NakedDesireCharacter.cpp +++ b/Source/NakedDesire/Player/NakedDesireCharacter.cpp @@ -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); diff --git a/Source/NakedDesire/Player/NakedDesireCharacter.h b/Source/NakedDesire/Player/NakedDesireCharacter.h index 63a3aabd..55e90a69 100644 --- a/Source/NakedDesire/Player/NakedDesireCharacter.h +++ b/Source/NakedDesire/Player/NakedDesireCharacter.h @@ -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); diff --git a/Source/NakedDesire/UI/GameLayoutWidget.cpp b/Source/NakedDesire/UI/GameLayoutWidget.cpp index c3706148..d4fd66fa 100644 --- a/Source/NakedDesire/UI/GameLayoutWidget.cpp +++ b/Source/NakedDesire/UI/GameLayoutWidget.cpp @@ -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(WardrobeScreenWidgetClass); } + +void UGameLayoutWidget::OpenPhone() +{ + PhoneScreenWidget = WidgetStack->AddWidget(PhoneScreenWidgetClass); +} diff --git a/Source/NakedDesire/UI/GameLayoutWidget.h b/Source/NakedDesire/UI/GameLayoutWidget.h index 311b2263..48a30bfb 100644 --- a/Source/NakedDesire/UI/GameLayoutWidget.h +++ b/Source/NakedDesire/UI/GameLayoutWidget.h @@ -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 WardrobeScreenWidget; - + + UPROPERTY(EditDefaultsOnly, Category = "UI") + TSubclassOf PhoneScreenWidgetClass; + + UPROPERTY() + TObjectPtr PhoneScreenWidget; + public: void OpenInventory(); void OpenWardrobe(); + void OpenPhone(); }; diff --git a/Source/NakedDesire/UI/Phone/PhoneAppIconWidget.cpp b/Source/NakedDesire/UI/Phone/PhoneAppIconWidget.cpp new file mode 100644 index 00000000..12b5c27e --- /dev/null +++ b/Source/NakedDesire/UI/Phone/PhoneAppIconWidget.cpp @@ -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 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); +} \ No newline at end of file diff --git a/Source/NakedDesire/UI/Phone/PhoneAppIconWidget.h b/Source/NakedDesire/UI/Phone/PhoneAppIconWidget.h new file mode 100644 index 00000000..63f9b3c8 --- /dev/null +++ b/Source/NakedDesire/UI/Phone/PhoneAppIconWidget.h @@ -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 InAppClass); + + DECLARE_DELEGATE_OneParam(FOnAppIconClicked, TSubclassOf); + FOnAppIconClicked OnClicked; + +private: + UPROPERTY(meta = (BindWidget)) + TObjectPtr IconButton; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr IconImage; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr NameText; + + UPROPERTY() + TSubclassOf AppClass; + + UFUNCTION() + void HandleClicked(); +}; \ No newline at end of file diff --git a/Source/NakedDesire/UI/Phone/PhoneAppWidget.h b/Source/NakedDesire/UI/Phone/PhoneAppWidget.h new file mode 100644 index 00000000..21fb77e7 --- /dev/null +++ b/Source/NakedDesire/UI/Phone/PhoneAppWidget.h @@ -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() +}; \ No newline at end of file diff --git a/Source/NakedDesire/UI/Phone/PhoneHomeScreenWidget.cpp b/Source/NakedDesire/UI/Phone/PhoneHomeScreenWidget.cpp new file mode 100644 index 00000000..11b97cdb --- /dev/null +++ b/Source/NakedDesire/UI/Phone/PhoneHomeScreenWidget.cpp @@ -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(this, AppIconWidgetClass); + Icon->Init(Entry.AppName, Entry.AppIcon, Entry.AppClass); + Icon->OnClicked.BindUObject(this, &UPhoneHomeScreenWidget::HandleAppIconClicked); + AppContainer->AddChild(Icon); + } +} + +void UPhoneHomeScreenWidget::HandleAppIconClicked(TSubclassOf AppClass) +{ + OnAppSelected.ExecuteIfBound(AppClass); +} \ No newline at end of file diff --git a/Source/NakedDesire/UI/Phone/PhoneHomeScreenWidget.h b/Source/NakedDesire/UI/Phone/PhoneHomeScreenWidget.h new file mode 100644 index 00000000..cf40de43 --- /dev/null +++ b/Source/NakedDesire/UI/Phone/PhoneHomeScreenWidget.h @@ -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 AppIcon; + + UPROPERTY(EditDefaultsOnly, Category = "Phone App") + TSubclassOf 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); + 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 AppContainer; + + UPROPERTY(EditDefaultsOnly, Category = "Phone") + TSubclassOf AppIconWidgetClass; + + UPROPERTY(EditDefaultsOnly, Category = "Phone") + TArray AppEntries; + + void HandleAppIconClicked(TSubclassOf AppClass); +}; \ No newline at end of file diff --git a/Source/NakedDesire/UI/Phone/PhoneScreenWidget.cpp b/Source/NakedDesire/UI/Phone/PhoneScreenWidget.cpp new file mode 100644 index 00000000..db550607 --- /dev/null +++ b/Source/NakedDesire/UI/Phone/PhoneScreenWidget.cpp @@ -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 AppClass) +{ + if (AppStack && AppClass) + { + AppStack->AddWidget(AppClass); + } +} + +UPhoneHomeScreenWidget* UPhoneScreenWidget::PushHomeScreen() +{ + if (!AppStack || !HomeScreenClass) + { + return nullptr; + } + + HomeScreen = AppStack->AddWidget(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(); +} \ No newline at end of file diff --git a/Source/NakedDesire/UI/Phone/PhoneScreenWidget.h b/Source/NakedDesire/UI/Phone/PhoneScreenWidget.h new file mode 100644 index 00000000..f87aa31f --- /dev/null +++ b/Source/NakedDesire/UI/Phone/PhoneScreenWidget.h @@ -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 AppClass); + +protected: + virtual void NativeOnActivated() override; + virtual UWidget* NativeGetDesiredFocusTarget() const override; + +private: + UPROPERTY(meta = (BindWidget)) + TObjectPtr AppStack; + + // The phone's physical home button — clears the app stack back to the home screen. + UPROPERTY(meta = (BindWidget)) + TObjectPtr HomeButton; + + UPROPERTY(EditDefaultsOnly, Category = "Phone") + TSubclassOf 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 HomeScreen; + + UPhoneHomeScreenWidget* PushHomeScreen(); + + UFUNCTION() + void GoHome(); +}; \ No newline at end of file