10 Commits

Author SHA1 Message Date
bc07c47557 feat(frontend): esqueleto CommonUI de MainMenu (boot→login→lobby)
Estrutura principal de front-end estilo Lyra portada para C++ proprio do
ZMMO (sem CommonGame), seguindo o padrao C++ Abstract -> WBP concreto:

- EZMMOFrontEndState / EZMMOLobbyPage e GameplayTags UI.Layer.* nativos
- UUIActivatableScreen_Base: base de tela (input config, hook de tema,
  voltar) espelhando o padrao de UUIButton_Base
- UUIPrimaryGameLayout_Base: switch principal (4 stacks CommonUI/camada)
- UUIManagerSubsystem (LocalPlayer): dono do root layout, idempotente
- UUIFrontEndFlowSubsystem (GameInstance): maquina de estados + driver
  de rede (Boot->Connecting->Login->ServerSelect->Lobby->EnteringWorld
  ->InWorld); Lobby e o hub logado, paginas internas = EZMMOLobbyPage
- UUIFrontEndScreenSet (DataAsset): mapa estado->tela, forge-ready
- AZMMOFrontEndGameMode + AZMMOFrontEndPlayerController
- GameInstance: bAutoConnectOnStart=false (conexao dirigida pelo fluxo)
- Build.cs: +GameplayTags; ARQUITETURA.md PR-first (s2.1/2.2/3.3/4.8/5)
- Assets: L_FrontEnd (GameMode via World Settings), WBP_PrimaryGameLayout,
  DA_FrontEndScreenSet; Config wiring

WBPs de pagina (Boot/Login/...) virao do Zeus UMG Forge depois (s4.8).
Smoke test PIE OK: root layout no viewport, fluxo Boot->Connecting,
no-op+log para telas nao configuradas, sem crash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:56:16 -03:00
ca781eff83 feat(ui): texturas grunge Aurora Arcana + painel texturizado wired (DT+WBP+teste)
4 UTexture2D procedurais (grunge: base escura + value-noise 2 oitavas + vinheta + manchas + moldura arcana) em Content/ZMMO/UI/CommonUI/Textures/Panels (Square/Horizontal/Vertical + Outline_Gold; sRGB, UI, no-mip). DT_UI_Styles linha Default: Style.Panel.PanelBackground/PanelOutline com FSlateBrush (DrawAs Box/9-slice, Margin 0.22) -> ResourceObject resolvido p/ as texturas (verificado via read_data_table). UI_Panel_Master tree = Background(Border)->Outline(Border) (identico ao Hyper, sem NamedSlot). UI_Test_Panel: PanelA/B/Wide com PanelTexture (Square/Horizontal/Vertical_01). Play L_Test_UI mostra paineis texturizados (modo textura usa FUIStyle do subsystem em runtime).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:04:30 -03:00
ca117a559e feat(ui): painel texturizado (padrao Hyper) coexistindo com modo cor
EUIPanelTexture (None, Rectangle_Horizontal_01/02/03, Vertical_01..04, Square_01, Vertical_Footer_01) - espelha o enum Panel do Hyper. FUIStylePanel ganha TMap<EUIPanelTexture,FSlateBrush> PanelBackground + PanelOutline (modelo Struct_UI_Style_Panels), mantendo os tokens de cor. UUIPanel_Base: prop PanelTexture + border Outline; RefreshUIStyle tenta modo TEXTURA (Find brush nos mapas -> SetBrush em Background/Outline) e cai no modo COR (RoundedBox) se nao houver brush. Compila OK. Texturas Aurora Arcana proprias serao geradas (nao importadas do Hyper).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:52:10 -03:00
22fc0fbcc6 test(ui): UI_Test_Panel - SizeBox->UI_Panel_Master direto (paineis preenchem a celula)
Border do painel encolhia (NamedSlot vazio + slot Overlay nao-Fill). Agora cada SizeBox tem o UI_Panel_Master como filho unico -> SizeBox forca 360x200 / 740x160 -> Background preenche a celula e mostra o tema. Botoes (Confirmar/Cancelar Danger/Fechar Ghost) em BtnRow separada. Limite MCP: slot Fill / NamedSlot exigem o Designer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:42:24 -03:00
78f2dd4144 test(ui): salva BP_TestUIHUD religado (BeginPlay->Create UI_Test_Panel->AddToViewport)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:39:02 -03:00
1d363caa96 test(ui): UI_Test_Panel mock de conteudo (SizeBox [ ][ ]/[ ]) + HUD repontado
Layout: Row1 = CellA/CellB (SizeBox 380x240, Overlay = UI_Panel_Master + botoes por cima); CellWide (SizeBox 780x180, painel Secondary + 3 botoes). PanelB=Card, PanelWide=Secondary. BP_TestUIHUD religado (BeginPlay->Create(UI_Test_Panel)->AddToViewport via GetOwningPlayerController). Layout e mock grosseiro (limite do MCP p/ slots) - ajuste fino no Designer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:38:44 -03:00
c6b0be92cc feat(ui): WBP UI_Panel_Master (parent UUIPanel_Base) Background->Content(NamedSlot)
Padrao replicado p/ 2a familia: C++ Abstract (UUIPanel_Base) -> WBP concreto. Arvore: Background(Border) -> Content(NamedSlot) via set_widget_tree; binds casam (Background/Content). Compila 0 erros, parent UUIPanel_Base. Designers injetam conteudo no NamedSlot Content; visual data-driven por FUIStyle.Panel/PanelType.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:29:17 -03:00
01c74c7683 feat(ui): UUIPanel_Base (UCommonUserWidget Abstract) data-driven por FUIStyle.Panel
Mesmo padrao do botao: C++ Abstract -> WBP concreto. PanelType (Primary/Secondary/Tertiary/Card) escolhe Bg/BgRaised/BgSunken/CardSelected; bRoundedBackground (RoundedBox + borda do tema); bUseSmallPadding. Background(UBorder) + Content(UNamedSlot) BindWidgetOptional. RefreshUIStyle resolve FUIStyle.Panel via UZMMOThemeSubsystem, reage a OnThemeChanged; BP_ApplyPanelStyle como hook. Compila OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:26:41 -03:00
f77223cfdb test(ui): setup PIE autocontido p/ UI_Test_Buttons (HUD+PC+GameMode+mapa)
BP_TestUIHUD: BeginPlay -> CreateWidget(UI_Test_Buttons, ctx/owner=GetOwningPlayerController) -> AddToViewport. BP_TestPC: bShowMouseCursor + click/mouseover events (p/ Hover/Pressed). GM_BP_TestGM: HUDClass=BP_TestUIHUD, PlayerControllerClass=BP_TestPC. L_Test_UI (Content/ZMMO/Debug/Maps): basic level com GameMode override = GM_BP_TestGM. Abrir L_Test_UI e dar Play exibe os botoes e permite testar estados. PIE confirmou HUD spawnado.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:22:27 -03:00
1ca546c9e9 test(ui): UI_Test_Buttons (variantes/estados/alinhamento) para smoke test
WBP em /Game/ZMMO/UI/Debug com 6 UI_Button_Master: Primary, Secondary, Danger, Ghost (so borda), Disabled (bIsEnabled=false), AlignLeft+Square (TextAlign=Left, bRoundedBackground=false). Compila 0 erros. Use o botao Play do WBP / PIE p/ exercitar Hover/Pressed/Disabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:12:32 -03:00
41 changed files with 1465 additions and 11 deletions

View File

@@ -203,10 +203,16 @@ Content/
│ │ ├── Environment/ ← Architecture/ Nature/ Props/ │ │ ├── Environment/ ← Architecture/ Nature/ Props/
│ │ └── Interactables/ ← baús, portas, warps, recursos │ │ └── Interactables/ ← baús, portas, warps, recursos
│ │ │ │
│ ├── Maps/ ← mapas que NÃO são de mundo
│ │ └── FrontEnd/ ← L_FrontEnd (boot/menu; GameMode via
│ │ World Settings = AZMMOFrontEndGameMode)
│ │
│ ├── UI/ ← HUD, Login, CharacterSelect, Inventory, │ ├── UI/ ← HUD, Login, CharacterSelect, Inventory,
│ │ │ Chat, Party, Guild, Trade, Quest, Map, │ │ │ Chat, Party, Guild, Trade, Quest, Map,
│ │ │ Shared, Icons │ │ │ Shared, Icons
│ │ ├── Shared/ ← WBPs reutilizáveis (UI_Button_Master…) │ │ ├── Shared/ ← WBPs reutilizáveis (UI_Button_Master…)
│ │ ├── FrontEnd/ ← WBP_PrimaryGameLayout, DA_FrontEndScreenSet,
│ │ │ telas refinadas (Boot/Login/… vindas do Forge)
│ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani) │ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani)
│ │ ├── CommonUI/Style/ ← estilos CommonUI (espelha o Hyper) │ │ ├── CommonUI/Style/ ← estilos CommonUI (espelha o Hyper)
│ │ │ ├── Button/ ← BSB_* (UCommonButtonStyle) │ │ │ ├── Button/ ← BSB_* (UCommonButtonStyle)
@@ -236,6 +242,9 @@ Content/
├── Developers/ ← rascunho por dev (não cooka) ├── Developers/ ← rascunho por dev (não cooka)
│ └── <nick-do-dev>/ │ └── <nick-do-dev>/
├── AutoCreated/ ← saída CRUA do Zeus UMG Forge (HTML→WBP).
│ └── <batch>/ Quarentena: refina-se e MOVE p/ ZMMO/UI/
│ FrontEnd/ antes de produção (não cooka cru)
├── ExternalContent/ ← Fab / Megascans / Marketplace ├── ExternalContent/ ← Fab / Megascans / Marketplace
│ ├── Fab/ Megascans/ Marketplace/ │ ├── Fab/ Megascans/ Marketplace/
├── Collections/ ├── Collections/
@@ -252,9 +261,20 @@ Source/ZMMO/
├── Game/ ← gameplay runtime (já existe) ├── Game/ ← gameplay runtime (já existe)
│ ├── Entity/ ← AZMMOEntity + derivados │ ├── Entity/ ← AZMMOEntity + derivados
│ ├── Controller/ ← AZMMOPlayerController │ ├── Controller/ ← AZMMOPlayerController,
├── Modes/ ← GameMode + GameInstance AZMMOFrontEndPlayerController
── Network/UZMMOWorldSubsystem ── Modes/ GameMode + GameInstance,
│ │ AZMMOFrontEndGameMode
│ ├── Network/ ← UZMMOWorldSubsystem
│ └── UI/ ← runtime de UI (não é contrato de dados)
│ ├── ZMMOThemeSubsystem.* ← resolução de tema
│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base
│ └── FrontEnd/ ← infra de navegação CommonUI:
│ UUIActivatableScreen_Base,
│ UUIPrimaryGameLayout_Base,
│ UUIManagerSubsystem (LocalPlayer),
│ UUIFrontEndFlowSubsystem (GameInstance),
│ UUIFrontEndScreenSet (UDataAsset)
└── Data/ ← contrato de dados (USTRUCT + UENUM) └── Data/ ← contrato de dados (USTRUCT + UENUM)
├── ZMMOData.h ← header agregador (opcional) ├── ZMMOData.h ← header agregador (opcional)
@@ -278,7 +298,9 @@ Source/ZMMO/
├── UI/ ├── UI/
│ ├── ThemeKeys.h ← EZMMOThemeKey (HUD_Frame, Login_BG…) │ ├── ThemeKeys.h ← EZMMOThemeKey (HUD_Frame, Login_BG…)
│ ├── ThemeRow.h ← FThemeEntry (Key → SoftAsset) │ ├── ThemeRow.h ← FThemeEntry (Key → SoftAsset)
── ThemeCalendarRow.h ← FThemeCalendarRow (data → ThemeId) ── ThemeCalendarRow.h ← FThemeCalendarRow (data → ThemeId)
│ ├── FrontEndTypes.h ← EZMMOFrontEndState, EZMMOLobbyPage
│ └── UILayerTags.h/.cpp ← GameplayTags nativos UI.Layer.*
└── World/ └── World/
└── ZoneRow.h └── ZoneRow.h
``` ```
@@ -365,6 +387,16 @@ base de widget = `UUI<Componente>_Base`, sempre `UCLASS(Abstract)` (não
instanciável direto; só os filhos derivam — padrão `_Abstract` do Hyper). instanciável direto; só os filhos derivam — padrão `_Abstract` do Hyper).
Ex.: `UUIButton_Base`, `UUIPanel_Base`, `UUIProgressBar_Base`. Ex.: `UUIButton_Base`, `UUIPanel_Base`, `UUIProgressBar_Base`.
**Sub-regra — infra de navegação de front-end:** as classes que orquestram a
navegação CommonUI seguem o mesmo prefixo `UUI` (sem `ZMMO`) por coerência com
a família de UI, **mesmo não sendo widgets**. Assim: o layout raiz é
`UUIPrimaryGameLayout_Base` (Abstract, widget), a base de tela é
`UUIActivatableScreen_Base` (Abstract, widget), e os subsistemas de UI são
`UUIManagerSubsystem` (`ULocalPlayerSubsystem`) e `UUIFrontEndFlowSubsystem`
(`UGameInstanceSubsystem`). Atores de gameplay do front-end (GameMode,
PlayerController) **mantêm** o prefixo `AZMMO` — não são UI, espelham
`AZMMOGameMode`: `AZMMOFrontEndGameMode`, `AZMMOFrontEndPlayerController`.
**Regra do UMG (importante):** um Widget Blueprint **não pode** herdar de outro **Regra do UMG (importante):** um Widget Blueprint **não pode** herdar de outro
Widget Blueprint que tenha árvore de widgets (UMG aborta a compilação: "apenas Widget Blueprint que tenha árvore de widgets (UMG aborta a compilação: "apenas
um deles deve ter árvore"). Portanto a camada abstrata é **só a classe C++** um deles deve ter árvore"). Portanto a camada abstrata é **só a classe C++**
@@ -577,6 +609,38 @@ o opcode de spawn. O cliente nunca instancia mob "por nome" — sempre via id.
O subsystem aplica fallback internamente: pergunta ao tema activo; se a O subsystem aplica fallback internamente: pergunta ao tema activo; se a
chave não estiver preenchida, devolve a do `Default`. chave não estiver preenchida, devolve a do `Default`.
### 4.8. Adicionar uma tela de front-end (ex: `Login`)
**Pré-requisito:** já existem `UUIActivatableScreen_Base` e
`UUIFrontEndFlowSubsystem` em `Source/ZMMO/Game/UI/FrontEnd/`, o
`WBP_PrimaryGameLayout` e o `DA_FrontEndScreenSet` em
`Content/ZMMO/UI/FrontEnd/`.
Fluxo geral: **Boot → Connecting → Login → ServerSelect → Lobby → EnteringWorld
→ InWorld**. O **Lobby** é o hub principal logado; suas páginas internas
(CharacterSelect, CharacterCreate, Shop, Settings…) são `EZMMOLobbyPage`,
navegadas por um stack CommonUI **dentro** da tela do Lobby — não são estados
de topo do fluxo.
1. Prototipar a tela em HTML/CSS em `Tools/Templates/MMO_Widget/` (ou usar os
templates já existentes).
2. Rodar o Zeus UMG Forge (HTML→UMG). Saída crua: `Content/AutoCreated/<batch>/`.
3. Abrir o `WBP_*` gerado e **reparent** a classe pai para
`UUIActivatableScreen_Base` (a base é `UCommonActivatableWidget`; o WBP tem
árvore própria — respeita §3.3, nunca encadeia WBP→WBP).
4. **Reconectar tema:** o Forge gera hard ref de textura (proibido §5).
Substituir por consumo via `UZMMOThemeSubsystem` por `EZMMOThemeKey` no
override de `RefreshUIStyle`; remontar botões/painéis com os widgets
compartilhados (`UI_Button_Master`, `UI_Panel_Master`).
5. **Mover** o asset refinado de `Content/AutoCreated/…` para
`Content/ZMMO/UI/FrontEnd/` (o cru não cooka).
6. Registrar no `DA_FrontEndScreenSet`: `StateScreens[EZMMOFrontEndState::Login]`
= soft class do WBP refinado (páginas do Lobby vão no map de
`EZMMOLobbyPage` da tela de Lobby). Sem registro, o flow faz no-op + log.
**Regra:** uma tela nunca segura hard ref a asset de tema (textura/som/VFX) —
sempre via `UZMMOThemeSubsystem` por `EZMMOThemeKey` (igual §4.7 e §5).
--- ---
## 5. O que **NÃO** fazer ## 5. O que **NÃO** fazer
@@ -588,7 +652,9 @@ chave não estiver preenchida, devolve a do `Default`.
- **Não** misturar assets de uma entidade com assets de outra. Cada - **Não** misturar assets de uma entidade com assets de outra. Cada
Wolf_Gray, Sword_Iron, etc. é uma pasta isolada. Wolf_Gray, Sword_Iron, etc. é uma pasta isolada.
- **Não** colocar nada na raiz `Content/`. Tudo é `Content/ZMMO/...` ou - **Não** colocar nada na raiz `Content/`. Tudo é `Content/ZMMO/...` ou
`Content/{Developers,ExternalContent}/...`. `Content/{Developers,AutoCreated,ExternalContent}/...`. `AutoCreated/` é
quarentena do Zeus UMG Forge: refina-se e **move** para `ZMMO/UI/FrontEnd/`
antes de produção; o cru não cooka.
- **Não** importar conteúdo do Fab/Megascans directo para dentro de `ZMMO/`. - **Não** importar conteúdo do Fab/Megascans directo para dentro de `ZMMO/`.
Vai sempre para `ExternalContent/` e, se quiser usar, **referencia** dali. Vai sempre para `ExternalContent/` e, se quiser usar, **referencia** dali.
- **Não** colocar dados tabulares em Blueprint (lista hardcoded de itens, - **Não** colocar dados tabulares em Blueprint (lista hardcoded de itens,
@@ -598,7 +664,10 @@ chave não estiver preenchida, devolve a do `Default`.
- **Não** colocar hard ref de textura/som de tema dentro de um Widget - **Não** colocar hard ref de textura/som de tema dentro de um Widget
(`TObjectPtr<UTexture2D> ChristmasFrame` é proibido). Widgets pedem ao (`TObjectPtr<UTexture2D> ChristmasFrame` é proibido). Widgets pedem ao
`UZMMOThemeSubsystem` via `EZMMOThemeKey`. Hard ref a asset de tema `UZMMOThemeSubsystem` via `EZMMOThemeKey`. Hard ref a asset de tema
bloqueia a troca em runtime e quebra a política de fallback. bloqueia a troca em runtime e quebra a política de fallback. Vale também
para **telas de front-end** (Boot/Login/ServerSelect/Lobby/…): a saída crua
do Forge traz hard ref — tem de ser reconectada ao subsystem antes do merge
(ver §4.8).
- **Não** criar chave de tema "ad-hoc" como `FName("HUD.Frame")`. Toda chave - **Não** criar chave de tema "ad-hoc" como `FName("HUD.Frame")`. Toda chave
é entrada do enum C++ `EZMMOThemeKey`. String literal é proibida — o é entrada do enum C++ `EZMMOThemeKey`. String literal é proibida — o
compilador tem de quebrar quando alguém esquecer de adicionar a chave. compilador tem de quebrar quando alguém esquecer de adicionar a chave.

View File

@@ -1,6 +1,6 @@
[/Script/EngineSettings.GameMapsSettings] [/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/ThirdPerson/Lvl_ThirdPerson.Lvl_ThirdPerson GameDefaultMap=/Game/ZMMO/Maps/FrontEnd/L_FrontEnd.L_FrontEnd
EditorStartupMap=/Game/ThirdPerson/Lvl_ThirdPerson.Lvl_ThirdPerson EditorStartupMap=/Game/ZMMO/Maps/FrontEnd/L_FrontEnd.L_FrontEnd
; GameMode/PlayerController/PlayerCharacter agora moram em C++ (Game/Modes/Entity/Controller). ; GameMode/PlayerController/PlayerCharacter agora moram em C++ (Game/Modes/Entity/Controller).
; O motor pode spawnar AZMMOGameMode directamente; um BP filho continua opcional. ; O motor pode spawnar AZMMOGameMode directamente; um BP filho continua opcional.
GlobalDefaultGameMode=/Script/ZMMO.ZMMOGameMode GlobalDefaultGameMode=/Script/ZMMO.ZMMOGameMode

View File

@@ -24,3 +24,8 @@ ProjectName=Third Person Game Template
;CalendarTable=/Game/ZMMO/UI/Themes/DT_ThemeCalendar.DT_ThemeCalendar ;CalendarTable=/Game/ZMMO/UI/Themes/DT_ThemeCalendar.DT_ThemeCalendar
UIStyleTable=/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles UIStyleTable=/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles
;DevThemeOverride=Christmas ;DevThemeOverride=Christmas
[/Script/ZMMO.UIFrontEndFlowSubsystem]
; Mapa estado->tela. O DA já existe (RootLayoutClass = WBP_PrimaryGameLayout);
; StateScreens fica vazio até os WBPs de página virem do Zeus UMG Forge (§4.8).
ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet

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.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "FrontEndTypes.generated.h"
/**
* Estados de topo do fluxo de front-end (MainMenu). Dirigidos pelo
* UUIFrontEndFlowSubsystem; cada estado resolve uma tela
* (UUIActivatableScreen_Base) via DA_FrontEndScreenSet.
*
* Fluxo: Boot → Connecting → Login → ServerSelect → Lobby → EnteringWorld
* → InWorld. O Lobby é o hub principal logado; suas páginas internas são
* EZMMOLobbyPage (não são estados de topo — ver ARQUITETURA.md §4.8).
*/
UENUM(BlueprintType)
enum class EZMMOFrontEndState : uint8
{
None,
Boot, // splash inicial enquanto subsistemas sobem
Connecting, // conectando ao servidor Zeus (UDP)
Login, // autenticação
ServerSelect, // escolha de servidor/realm
Lobby, // hub principal logado (host das EZMMOLobbyPage)
EnteringWorld, // loading/handoff: OpenLevel do mapa de mundo
InWorld // no mundo (HUD de gameplay assume)
};
/**
* Páginas internas do Lobby — navegadas por um stack CommonUI DENTRO da tela
* de Lobby ("Switch principal"), não pelo fluxo de topo.
*/
UENUM(BlueprintType)
enum class EZMMOLobbyPage : uint8
{
None,
CharacterSelect,
CharacterCreate,
Shop,
Settings
};

View File

@@ -0,0 +1,9 @@
#include "UI/UILayerTags.h"
namespace ZMMOUITags
{
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Game, "UI.Layer.Game");
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_GameMenu, "UI.Layer.GameMenu");
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Menu, "UI.Layer.Menu");
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Modal, "UI.Layer.Modal");
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "NativeGameplayTags.h"
/**
* GameplayTags nativos das camadas do switch principal de UI. Declaração
* nativa (não depende de DefaultGameplayTags.ini) — registradas no startup
* do módulo. Mapeadas para os stacks do UUIPrimaryGameLayout_Base.
*
* UI.Layer.Game — HUD de gameplay (camada de fundo)
* UI.Layer.GameMenu — menus sobre o jogo (inventário, mapa…)
* UI.Layer.Menu — front-end (Boot/Login/ServerSelect/Lobby)
* UI.Layer.Modal — diálogos/loading por cima de tudo
*/
namespace ZMMOUITags
{
ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Game);
ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_GameMenu);
ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Menu);
ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Modal);
}

View File

@@ -2,6 +2,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Fonts/SlateFontInfo.h" #include "Fonts/SlateFontInfo.h"
#include "Styling/SlateBrush.h"
#include "Layout/Margin.h" #include "Layout/Margin.h"
#include "Templates/SubclassOf.h" #include "Templates/SubclassOf.h"
#include "UIStyleTypes.h" #include "UIStyleTypes.h"
@@ -331,6 +332,17 @@ struct ZMMO_API FUIStylePanel
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FLinearColor CardSelectedBg = FLinearColor(FColor(24, 32, 58)); FLinearColor CardSelectedBg = FLinearColor(FColor(24, 32, 58));
/**
* Modo texturizado (espelha Struct_UI_Style_Panels do Hyper): brush por
* EUIPanelTexture. PanelBackground = fundo; PanelOutline = contorno.
* Se vazio p/ a chave, o painel usa o modo cor (RoundedBox) acima.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
TMap<EUIPanelTexture, FSlateBrush> PanelBackground;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
TMap<EUIPanelTexture, FSlateBrush> PanelOutline;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)

View File

@@ -52,6 +52,25 @@ enum class EUIPanelType : uint8
Card UMETA(DisplayName = "Card") Card UMETA(DisplayName = "Card")
}; };
/**
* Conjunto de textura/silhueta do painel (espelha o enum "Panel" do Hyper).
* Indexa os mapas de FSlateBrush em FUIStylePanel (bg + outline).
*/
UENUM(BlueprintType)
enum class EUIPanelTexture : uint8
{
None UMETA(DisplayName = "None"),
Rectangle_Horizontal_01 UMETA(DisplayName = "Rectangle Horizontal 01"),
Rectangle_Horizontal_02 UMETA(DisplayName = "Rectangle Horizontal 02"),
Rectangle_Horizontal_03 UMETA(DisplayName = "Rectangle Horizontal 03"),
Rectangle_Vertical_01 UMETA(DisplayName = "Rectangle Vertical 01"),
Rectangle_Vertical_02 UMETA(DisplayName = "Rectangle Vertical 02"),
Rectangle_Vertical_03 UMETA(DisplayName = "Rectangle Vertical 03"),
Rectangle_Vertical_04 UMETA(DisplayName = "Rectangle Vertical 04"),
Rectangle_Square_01 UMETA(DisplayName = "Rectangle Square 01"),
Vertical_Footer_01 UMETA(DisplayName = "Vertical Footer 01")
};
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EUIProgressBarType : uint8 enum class EUIProgressBarType : uint8
{ {

View File

@@ -0,0 +1,24 @@
#include "ZMMOFrontEndPlayerController.h"
#include "UIFrontEndFlowSubsystem.h"
#include "Engine/GameInstance.h"
AZMMOFrontEndPlayerController::AZMMOFrontEndPlayerController()
{
bShowMouseCursor = true;
}
void AZMMOFrontEndPlayerController::BeginPlay()
{
Super::BeginPlay();
SetInputMode(FInputModeUIOnly());
if (const UGameInstance* GI = GetGameInstance())
{
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
{
Flow->StartFrontEnd();
}
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ZMMOFrontEndPlayerController.generated.h"
/**
* AZMMOFrontEndPlayerController
*
* Controller do mapa de front-end. Não herda de AZMMOPlayerController (sem
* Enhanced Input de gameplay/touch). Em BeginPlay arranca o fluxo de
* front-end (UUIFrontEndFlowSubsystem) — feito aqui, e não no
* GameInstance::Init, porque os subsistemas já existem neste ponto (ver
* aviso no header do ZeusNetworkSubsystem).
*/
UCLASS(Blueprintable, BlueprintType)
class ZMMO_API AZMMOFrontEndPlayerController : public APlayerController
{
GENERATED_BODY()
public:
AZMMOFrontEndPlayerController();
protected:
virtual void BeginPlay() override;
};

View File

@@ -0,0 +1,10 @@
#include "ZMMOFrontEndGameMode.h"
#include "ZMMOFrontEndPlayerController.h"
AZMMOFrontEndGameMode::AZMMOFrontEndGameMode()
{
// UI pura: sem pawn. O controller cria o root layout e arranca o fluxo.
DefaultPawnClass = nullptr;
PlayerControllerClass = AZMMOFrontEndPlayerController::StaticClass();
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "ZMMOFrontEndGameMode.generated.h"
/**
* AZMMOFrontEndGameMode
*
* GameMode do mapa de front-end (L_FrontEnd): só UI, sem pawn nem network
* play. NÃO é o GameMode global (esse continua AZMMOGameMode para mapas de
* mundo) — aplicado via override no World Settings de L_FrontEnd
* (ARQUITETURA.md §3.3 / decisão de mapa dedicado).
*/
UCLASS(Blueprintable, BlueprintType)
class ZMMO_API AZMMOFrontEndGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AZMMOFrontEndGameMode();
};

View File

@@ -32,11 +32,12 @@ public:
/** /**
* Se `true`, tenta ligar ao servidor Zeus em `Init()` usando * Se `true`, tenta ligar ao servidor Zeus em `Init()` usando
* `ZeusServerHost`/`ZeusServerPort`. Default `true` em V0 (smoke test); * `ZeusServerHost`/`ZeusServerPort`. Default `false`: a conexao agora e
* passar a `false` quando existir UI de login que controle a conexao. * dirigida pelo `UUIFrontEndFlowSubsystem` (fluxo Boot→Connecting→Login).
* Pôr `true` apenas para smoke test legado sem front-end.
*/ */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network") UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network")
bool bAutoConnectOnStart = true; bool bAutoConnectOnStart = false;
/** Host (IPv4 ou hostname) do servidor Zeus. */ /** Host (IPv4 ou hostname) do servidor Zeus. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network") UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network")

View File

@@ -0,0 +1,99 @@
#include "UIActivatableScreen_Base.h"
#include "ZMMOThemeSubsystem.h"
#include "UIFrontEndFlowSubsystem.h"
#include "Engine/GameInstance.h"
#include "Input/UIActionBindingHandle.h"
#include "CommonInputModeTypes.h"
TOptional<FUIInputConfig> UUIActivatableScreen_Base::GetDesiredInputConfig() const
{
switch (InputConfig)
{
case EZMMOScreenInputMode::GameAndMenu:
return FUIInputConfig(ECommonInputMode::All, GameMouseCaptureMode);
case EZMMOScreenInputMode::Game:
return FUIInputConfig(ECommonInputMode::Game, GameMouseCaptureMode);
case EZMMOScreenInputMode::Menu:
return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
case EZMMOScreenInputMode::Default:
default:
return TOptional<FUIInputConfig>();
}
}
void UUIActivatableScreen_Base::RefreshUIStyle_Implementation()
{
// Base no-op: cada WBP/filho sobrescreve para reaplicar tokens do tema
// ativo (via UZMMOThemeSubsystem). Mantém o contrato igual a
// UUIButton_Base::RefreshUIStyle / UUIPanel_Base::RefreshUIStyle.
}
void UUIActivatableScreen_Base::NativeConstruct()
{
Super::NativeConstruct();
if (!bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.AddDynamic(this, &UUIActivatableScreen_Base::HandleThemeChanged);
bThemeBound = true;
}
}
}
RefreshUIStyle();
}
void UUIActivatableScreen_Base::NativeDestruct()
{
if (bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.RemoveDynamic(this, &UUIActivatableScreen_Base::HandleThemeChanged);
}
}
bThemeBound = false;
}
Super::NativeDestruct();
}
void UUIActivatableScreen_Base::NativeOnActivated()
{
Super::NativeOnActivated();
RefreshUIStyle();
BP_OnScreenActivated();
}
void UUIActivatableScreen_Base::NativeOnDeactivated()
{
Super::NativeOnDeactivated();
BP_OnScreenDeactivated();
}
bool UUIActivatableScreen_Base::NativeOnHandleBackAction()
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
{
if (Flow->RequestBack())
{
return true; // consumido pelo fluxo
}
}
}
return Super::NativeOnHandleBackAction();
}
void UUIActivatableScreen_Base::HandleThemeChanged(FName /*NewThemeId*/)
{
RefreshUIStyle();
}

View File

@@ -0,0 +1,77 @@
#pragma once
#include "CoreMinimal.h"
#include "CommonActivatableWidget.h"
#include "Engine/EngineBaseTypes.h"
#include "UIActivatableScreen_Base.generated.h"
/**
* Modo de input desejado pela tela (espelha o padrão do Lyra). O
* UUIActivatableScreen_Base converte isto num FUIInputConfig em
* GetDesiredInputConfig.
*/
UENUM(BlueprintType)
enum class EZMMOScreenInputMode : uint8
{
Default, // não força nada (herda do CommonUI)
GameAndMenu, // jogo + UI (ex.: HUD com cursor)
Game, // só jogo
Menu // só UI (telas de front-end — default)
};
/**
* Base de TODAS as telas de front-end (Boot/Login/ServerSelect/Lobby/…).
*
* Fundação CommonUI (UCommonActivatableWidget) cuida de
* ativação/foco/voltar/stack. Camada Abstract; o WBP concreto herda DIRETO
* desta classe C++ (UMG não encadeia árvores — ARQUITETURA.md §3.3). Os WBPs
* gerados pelo Zeus UMG Forge são reparented para cá (§4.8).
*
* Reage à troca de tema via UZMMOThemeSubsystem (mesmo padrão de
* UUIButton_Base) e encaminha o "voltar" ao UUIFrontEndFlowSubsystem.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUIActivatableScreen_Base : public UCommonActivatableWidget
{
GENERATED_BODY()
public:
/** Re-resolve os tokens do tema ativo e reaplica. WBP/filho sobrescreve. */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "UI Style")
void RefreshUIStyle();
virtual void RefreshUIStyle_Implementation();
protected:
// ---- Input config (desktop/gamepad) ----
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
EZMMOScreenInputMode InputConfig = EZMMOScreenInputMode::Menu;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
EMouseCaptureMode GameMouseCaptureMode = EMouseCaptureMode::CapturePermanently;
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
// ---- Lifecycle (espelha o hook de tema de UUIButton_Base) ----
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
virtual void NativeOnActivated() override;
virtual void NativeOnDeactivated() override;
/** "Voltar" (Esc/B): encaminha ao UUIFrontEndFlowSubsystem::RequestBack. */
virtual bool NativeOnHandleBackAction() override;
/** Hooks Blueprint para o WBP refinado. */
UFUNCTION(BlueprintImplementableEvent, Category = "UI",
meta = (DisplayName = "On Screen Activated"))
void BP_OnScreenActivated();
UFUNCTION(BlueprintImplementableEvent, Category = "UI",
meta = (DisplayName = "On Screen Deactivated"))
void BP_OnScreenDeactivated();
private:
UFUNCTION()
void HandleThemeChanged(FName NewThemeId);
bool bThemeBound = false;
};

View File

@@ -0,0 +1,339 @@
#include "UIFrontEndFlowSubsystem.h"
#include "ZMMO.h"
#include "UIFrontEndScreenSet.h"
#include "UIManagerSubsystem.h"
#include "UIPrimaryGameLayout_Base.h"
#include "UI/UILayerTags.h"
#include "ZMMOGameInstance.h"
#include "ZMMOThemeSubsystem.h"
#include "ZeusNetworkSubsystem.h"
#include "CommonActivatableWidget.h"
#include "Engine/GameInstance.h"
#include "Engine/LocalPlayer.h"
#include "UObject/UObjectGlobals.h"
#include "Engine/AssetManager.h"
#include "Engine/StreamableManager.h"
#include "Kismet/GameplayStatics.h"
void UUIFrontEndFlowSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// Detecta chegada ao mundo após o OpenLevel disparado por travel.
PostLoadMapHandle = FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(
this, &UUIFrontEndFlowSubsystem::HandlePostLoadMap);
}
void UUIFrontEndFlowSubsystem::Deinitialize()
{
if (PostLoadMapHandle.IsValid())
{
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(PostLoadMapHandle);
PostLoadMapHandle.Reset();
}
UnbindNetwork();
Super::Deinitialize();
}
void UUIFrontEndFlowSubsystem::StartFrontEnd()
{
UUIFrontEndScreenSet* SS = GetScreenSet();
if (UUIManagerSubsystem* Mgr = GetUIManager())
{
TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass;
if (SS && !SS->RootLayoutClass.IsNull())
{
LayoutClass = SS->RootLayoutClass.LoadSynchronous();
}
Mgr->CreateAndAddRootLayout(LayoutClass);
}
BindNetwork();
SetState(EZMMOFrontEndState::Boot);
// Substitui o auto-connect do GameInstance: o fluxo dirige a conexão.
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
{
FString Host = TEXT("127.0.0.1");
int32 Port = 27777;
if (const UZMMOGameInstance* GI = Cast<UZMMOGameInstance>(GetGameInstance()))
{
Host = GI->ZeusServerHost;
Port = GI->ZeusServerPort;
}
SetState(EZMMOFrontEndState::Connecting);
Zeus->ConnectToZeusServer(Host, Port);
}
else
{
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: ZeusNetworkSubsystem indisponível; sem conexão."));
}
}
void UUIFrontEndFlowSubsystem::SetState(EZMMOFrontEndState NewState)
{
if (NewState == CurrentState)
{
return;
}
CurrentState = NewState;
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: estado → %s"),
*UEnum::GetValueAsString(NewState));
OnStateChanged.Broadcast(NewState);
const FGameplayTag MenuLayer = ZMMOUITags::UI_Layer_Menu.GetTag();
const FGameplayTag ModalLayer = ZMMOUITags::UI_Layer_Modal.GetTag();
switch (NewState)
{
case EZMMOFrontEndState::Boot:
case EZMMOFrontEndState::Connecting:
case EZMMOFrontEndState::Login:
case EZMMOFrontEndState::ServerSelect:
case EZMMOFrontEndState::Lobby:
// Telas do front-end vivem na camada Menu (uma por vez).
if (UUIManagerSubsystem* Mgr = GetUIManager())
{
Mgr->ClearLayer(MenuLayer);
}
ResolveAndPushScreen(NewState);
break;
case EZMMOFrontEndState::EnteringWorld:
// Loading/handoff por cima de tudo (camada Modal).
ResolveAndPushScreen(NewState);
break;
case EZMMOFrontEndState::InWorld:
// HUD de gameplay assume — limpa front-end e loading.
if (UUIManagerSubsystem* Mgr = GetUIManager())
{
Mgr->ClearLayer(ModalLayer);
Mgr->ClearLayer(MenuLayer);
}
break;
case EZMMOFrontEndState::None:
default:
break;
}
}
void UUIFrontEndFlowSubsystem::ShowLobbyPage(EZMMOLobbyPage Page)
{
UUIFrontEndScreenSet* SS = GetScreenSet();
UUIManagerSubsystem* Mgr = GetUIManager();
if (!SS || !Mgr)
{
return;
}
const TSoftClassPtr<UCommonActivatableWidget> Soft = SS->GetLobbyPage(Page);
if (Soft.IsNull())
{
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: página de Lobby %s não configurada (DA vazio)."),
*UEnum::GetValueAsString(Page));
return;
}
const FGameplayTag MenuLayer = ZMMOUITags::UI_Layer_Menu.GetTag();
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
UAssetManager::GetStreamableManager().RequestAsyncLoad(
Path,
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, MenuLayer]()
{
if (UUIManagerSubsystem* M = GetUIManager())
{
if (UClass* Cls = Soft.Get())
{
M->PushScreenToLayer(MenuLayer, Cls);
}
}
}));
}
bool UUIFrontEndFlowSubsystem::RequestBack()
{
switch (CurrentState)
{
case EZMMOFrontEndState::ServerSelect:
SetState(EZMMOFrontEndState::Login);
return true;
case EZMMOFrontEndState::Lobby:
SetState(EZMMOFrontEndState::ServerSelect);
return true;
default:
return false;
}
}
void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State)
{
UUIFrontEndScreenSet* SS = GetScreenSet();
if (!SS)
{
UE_LOG(LogZMMO, Warning,
TEXT("FrontEndFlow: ScreenSet não configurado; tela de %s ignorada."),
*UEnum::GetValueAsString(State));
return;
}
const TSoftClassPtr<UCommonActivatableWidget> Soft = SS->GetScreenForState(State);
if (Soft.IsNull())
{
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: Screen for state %s not configured."),
*UEnum::GetValueAsString(State));
return;
}
UUIManagerSubsystem* Mgr = GetUIManager();
if (!Mgr)
{
return;
}
const FGameplayTag Layer = (State == EZMMOFrontEndState::EnteringWorld)
? ZMMOUITags::UI_Layer_Modal.GetTag()
: ZMMOUITags::UI_Layer_Menu.GetTag();
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
UAssetManager::GetStreamableManager().RequestAsyncLoad(
Path,
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer]()
{
if (UUIManagerSubsystem* M = GetUIManager())
{
if (UClass* Cls = Soft.Get())
{
M->PushScreenToLayer(Layer, Cls);
}
}
}));
}
UZeusNetworkSubsystem* UUIFrontEndFlowSubsystem::GetZeusNetwork() const
{
const UGameInstance* GI = GetGameInstance();
return GI ? GI->GetSubsystem<UZeusNetworkSubsystem>() : nullptr;
}
UUIManagerSubsystem* UUIFrontEndFlowSubsystem::GetUIManager() const
{
if (const UGameInstance* GI = GetGameInstance())
{
if (ULocalPlayer* LP = GI->GetFirstGamePlayer())
{
return LP->GetSubsystem<UUIManagerSubsystem>();
}
}
return nullptr;
}
UUIFrontEndScreenSet* UUIFrontEndFlowSubsystem::GetScreenSet()
{
if (ScreenSet)
{
return ScreenSet;
}
if (!ScreenSetAsset.IsNull())
{
ScreenSet = ScreenSetAsset.LoadSynchronous();
}
else
{
UE_LOG(LogZMMO, Warning,
TEXT("FrontEndFlow: ScreenSetAsset não setado em DefaultGame.ini; telas farão no-op."));
}
return ScreenSet;
}
void UUIFrontEndFlowSubsystem::BindNetwork()
{
if (bNetBound)
{
return;
}
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
{
Zeus->OnConnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected);
Zeus->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
Zeus->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleDisconnected);
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
bNetBound = true;
}
}
void UUIFrontEndFlowSubsystem::UnbindNetwork()
{
if (!bNetBound)
{
return;
}
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
{
Zeus->OnConnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected);
Zeus->OnConnectionFailed.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
Zeus->OnDisconnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleDisconnected);
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
}
bNetBound = false;
}
void UUIFrontEndFlowSubsystem::HandleConnected()
{
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: conectado → Login."));
SetState(EZMMOFrontEndState::Login);
}
void UUIFrontEndFlowSubsystem::HandleConnectionFailed(FString Reason)
{
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão falhou (%s) → volta a Login."), *Reason);
SetState(EZMMOFrontEndState::Login);
}
void UUIFrontEndFlowSubsystem::HandleDisconnected()
{
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: desconectado."));
if (CurrentState != EZMMOFrontEndState::InWorld)
{
SetState(EZMMOFrontEndState::Login);
}
}
void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapName, const FString& MapPath)
{
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: travel pedido pelo servidor → %s (%s)."), *MapName, *MapPath);
bTravelingToWorld = true;
SetState(EZMMOFrontEndState::EnteringWorld);
if (!MapPath.IsEmpty())
{
UGameplayStatics::OpenLevel(GetGameInstance(), FName(*MapPath));
}
}
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
{
if (bTravelingToWorld)
{
bTravelingToWorld = false;
SetState(EZMMOFrontEndState::InWorld);
}
}
void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId)
{
// Dívida técnica (D5): ainda não há gancho no ZeusNetworkSubsystem para o
// ThemeId do ServerHello. Mantido pronto para quando o plugin expuser.
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->ApplyServerTheme(ThemeId);
}
}
}

View File

@@ -0,0 +1,109 @@
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "UI/FrontEndTypes.h"
#include "UIFrontEndFlowSubsystem.generated.h"
class UUIFrontEndScreenSet;
class UUIManagerSubsystem;
class UZeusNetworkSubsystem;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOFrontEndStateChanged, EZMMOFrontEndState, NewState);
/**
* Orquestrador do fluxo de front-end. UGameInstanceSubsystem porque o
* GameInstance persiste o travel boot→mundo (a máquina de estados não pode
* morrer no OpenLevel).
*
* Responsável por: (a) dirigir a conexão via UZeusNetworkSubsystem;
* (b) manter EZMMOFrontEndState; (c) resolver estado→tela pelo
* DA_FrontEndScreenSet e pedir ao UUIManagerSubsystem para empurrar na
* camada certa; (d) detectar a chegada ao mundo.
*
* As telas (WBP) ainda não existem — virão do Zeus UMG Forge (§4.8).
* Enquanto não registradas no DA, cada transição faz no-op + log.
*/
UCLASS(Config = Game)
class ZMMO_API UUIFrontEndFlowSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// USubsystem
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
/**
* Ponto de entrada chamado pelo AZMMOFrontEndPlayerController::BeginPlay
* (garante que os subsistemas já existem — ver aviso no header do
* ZeusNetworkSubsystem sobre GameInstance::Init). Cria o root layout,
* liga os delegates de rede e dispara Boot→Connecting.
*/
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void StartFrontEnd();
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void SetState(EZMMOFrontEndState NewState);
UFUNCTION(BlueprintPure, Category = "FrontEnd")
EZMMOFrontEndState GetCurrentState() const { return CurrentState; }
/** Mostra uma página interna do Lobby ("switch principal"). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void ShowLobbyPage(EZMMOLobbyPage Page);
/** "Voltar". Retorna true se o fluxo consumiu (tela base encaminha aqui). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
bool RequestBack();
UPROPERTY(BlueprintAssignable, Category = "FrontEnd")
FOnZMMOFrontEndStateChanged OnStateChanged;
protected:
/** DA com o mapa estado→tela. Config em DefaultGame.ini. */
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd")
TSoftObjectPtr<UUIFrontEndScreenSet> ScreenSetAsset;
private:
void BindNetwork();
void UnbindNetwork();
UZeusNetworkSubsystem* GetZeusNetwork() const;
UUIManagerSubsystem* GetUIManager() const;
UUIFrontEndScreenSet* GetScreenSet();
/** Resolve a soft class do estado e empurra na camada Menu (no-op+log se vazio). */
void ResolveAndPushScreen(EZMMOFrontEndState State);
UFUNCTION()
void HandleConnected();
UFUNCTION()
void HandleConnectionFailed(FString Reason);
UFUNCTION()
void HandleDisconnected();
UFUNCTION()
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
void HandlePostLoadMap(UWorld* LoadedWorld);
/**
* Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
* → UZMMOThemeSubsystem::ApplyServerTheme. Sem isto, o tema resolve por
* calendário/Default (ARQUITETURA.md §1.10).
*/
void HandleServerHelloTheme(FName ThemeId);
UPROPERTY(Transient)
EZMMOFrontEndState CurrentState = EZMMOFrontEndState::None;
UPROPERTY(Transient)
TObjectPtr<UUIFrontEndScreenSet> ScreenSet;
bool bNetBound = false;
bool bTravelingToWorld = false;
FDelegateHandle PostLoadMapHandle;
};

View File

@@ -0,0 +1,19 @@
#include "UIFrontEndScreenSet.h"
TSoftClassPtr<UCommonActivatableWidget> UUIFrontEndScreenSet::GetScreenForState(EZMMOFrontEndState State) const
{
if (const TSoftClassPtr<UCommonActivatableWidget>* Found = StateScreens.Find(State))
{
return *Found;
}
return TSoftClassPtr<UCommonActivatableWidget>();
}
TSoftClassPtr<UCommonActivatableWidget> UUIFrontEndScreenSet::GetLobbyPage(EZMMOLobbyPage Page) const
{
if (const TSoftClassPtr<UCommonActivatableWidget>* Found = LobbyPages.Find(Page))
{
return *Found;
}
return TSoftClassPtr<UCommonActivatableWidget>();
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Templates/SubclassOf.h"
#include "UI/FrontEndTypes.h"
#include "UIFrontEndScreenSet.generated.h"
class UCommonActivatableWidget;
class UUIPrimaryGameLayout_Base;
/**
* Mapa data-driven estado → tela. Desacopla o UUIFrontEndFlowSubsystem das
* classes concretas: as telas (WBP) ainda não existem — serão geradas pelo
* Zeus UMG Forge, refinadas e registradas aqui depois (ARQUITETURA.md §4.8).
* Enquanto o registro estiver vazio, o flow faz no-op + log.
*
* Asset concreto: DA_FrontEndScreenSet em Content/ZMMO/UI/FrontEnd/.
*/
UCLASS(BlueprintType)
class ZMMO_API UUIFrontEndScreenSet : public UDataAsset
{
GENERATED_BODY()
public:
/** WBP de layout raiz (herda UUIPrimaryGameLayout_Base). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd")
TSoftClassPtr<UUIPrimaryGameLayout_Base> RootLayoutClass;
/** Tela por estado de topo do fluxo. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd")
TMap<EZMMOFrontEndState, TSoftClassPtr<UCommonActivatableWidget>> StateScreens;
/** Páginas internas do Lobby (host = a tela de Lobby; "switch principal"). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd")
TMap<EZMMOLobbyPage, TSoftClassPtr<UCommonActivatableWidget>> LobbyPages;
/** Soft class da tela do estado (vazio se não configurado). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
TSoftClassPtr<UCommonActivatableWidget> GetScreenForState(EZMMOFrontEndState State) const;
/** Soft class da página de Lobby (vazio se não configurado). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
TSoftClassPtr<UCommonActivatableWidget> GetLobbyPage(EZMMOLobbyPage Page) const;
};

View File

@@ -0,0 +1,95 @@
#include "UIManagerSubsystem.h"
#include "ZMMO.h"
#include "UIPrimaryGameLayout_Base.h"
#include "CommonActivatableWidget.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/PlayerController.h"
#include "Blueprint/UserWidget.h"
namespace
{
constexpr int32 GRootLayoutZOrder = 1000;
}
void UUIManagerSubsystem::CreateAndAddRootLayout(TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass)
{
// Idempotente: sobrevive ao travel boot→mundo sem recriar.
if (RootLayout && RootLayout->IsInViewport())
{
return;
}
if (!LayoutClass)
{
UE_LOG(LogZMMO, Warning,
TEXT("UIManagerSubsystem: RootLayoutClass não configurado (DA_FrontEndScreenSet vazio?). Sem root layout."));
return;
}
ULocalPlayer* LP = GetLocalPlayer();
if (!LP)
{
return;
}
APlayerController* PC = LP->GetPlayerController(LP->GetWorld());
if (!PC)
{
UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: PlayerController ausente; root layout adiado."));
return;
}
RootLayout = CreateWidget<UUIPrimaryGameLayout_Base>(PC, LayoutClass);
if (!RootLayout)
{
UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: falha ao criar o root layout."));
return;
}
RootLayout->AddToViewport(GRootLayoutZOrder);
UE_LOG(LogZMMO, Log, TEXT("UIManagerSubsystem: root layout criado e adicionado ao viewport."));
}
bool UUIManagerSubsystem::IsRootLayoutReady() const
{
return RootLayout != nullptr && RootLayout->IsInViewport();
}
UCommonActivatableWidget* UUIManagerSubsystem::PushScreenToLayer(
FGameplayTag LayerTag,
TSubclassOf<UCommonActivatableWidget> WidgetClass)
{
if (!RootLayout)
{
UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: PushScreenToLayer sem root layout."));
return nullptr;
}
return RootLayout->PushWidgetToLayer(LayerTag, WidgetClass);
}
void UUIManagerSubsystem::PopScreenFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget)
{
if (RootLayout)
{
RootLayout->PopWidgetFromLayer(LayerTag, Widget);
}
}
void UUIManagerSubsystem::ClearLayer(FGameplayTag LayerTag)
{
if (RootLayout)
{
RootLayout->ClearLayer(LayerTag);
}
}
void UUIManagerSubsystem::Deinitialize()
{
if (RootLayout)
{
RootLayout->RemoveFromParent();
RootLayout = nullptr;
}
Super::Deinitialize();
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/LocalPlayerSubsystem.h"
#include "GameplayTagContainer.h"
#include "Templates/SubclassOf.h"
#include "UIManagerSubsystem.generated.h"
class UCommonActivatableWidget;
class UUIPrimaryGameLayout_Base;
/**
* Dono do layout raiz para o LocalPlayer. ULocalPlayerSubsystem porque o
* LocalPlayer persiste entre mapas (non-seamless do mesmo GameInstance) e
* troca de PlayerController — o root layout sobrevive ao travel
* boot→mundo sem ser recriado (idempotente).
*
* É "burro": recebe classes já resolvidas. Quem resolve estado→tela e dirige
* a navegação é o UUIFrontEndFlowSubsystem.
*/
UCLASS()
class ZMMO_API UUIManagerSubsystem : public ULocalPlayerSubsystem
{
GENERATED_BODY()
public:
/** Cria o root layout e adiciona ao viewport. Idempotente. */
UFUNCTION(BlueprintCallable, Category = "UI|Manager")
void CreateAndAddRootLayout(TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass);
UFUNCTION(BlueprintPure, Category = "UI|Manager")
bool IsRootLayoutReady() const;
UFUNCTION(BlueprintPure, Category = "UI|Manager")
UUIPrimaryGameLayout_Base* GetRootLayout() const { return RootLayout; }
/** Empurra a widget no topo do stack da camada. */
UFUNCTION(BlueprintCallable, Category = "UI|Manager")
UCommonActivatableWidget* PushScreenToLayer(
FGameplayTag LayerTag,
TSubclassOf<UCommonActivatableWidget> WidgetClass);
UFUNCTION(BlueprintCallable, Category = "UI|Manager")
void PopScreenFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget);
UFUNCTION(BlueprintCallable, Category = "UI|Manager")
void ClearLayer(FGameplayTag LayerTag);
// USubsystem
virtual void Deinitialize() override;
private:
UPROPERTY(Transient)
TObjectPtr<UUIPrimaryGameLayout_Base> RootLayout;
};

View File

@@ -0,0 +1,64 @@
#include "UIPrimaryGameLayout_Base.h"
#include "CommonActivatableWidget.h"
#include "Widgets/CommonActivatableWidgetContainer.h"
#include "UI/UILayerTags.h"
void UUIPrimaryGameLayout_Base::NativeOnInitialized()
{
Super::NativeOnInitialized();
RegisterLayer(ZMMOUITags::UI_Layer_Game, Stack_Game);
RegisterLayer(ZMMOUITags::UI_Layer_GameMenu, Stack_GameMenu);
RegisterLayer(ZMMOUITags::UI_Layer_Menu, Stack_Menu);
RegisterLayer(ZMMOUITags::UI_Layer_Modal, Stack_Modal);
}
void UUIPrimaryGameLayout_Base::RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetStack* Stack)
{
if (Stack)
{
Layers.Add(LayerTag, Stack);
}
}
UCommonActivatableWidgetStack* UUIPrimaryGameLayout_Base::GetLayerStack(FGameplayTag LayerTag) const
{
const TObjectPtr<UCommonActivatableWidgetStack>* Found = Layers.Find(LayerTag);
return Found ? *Found : nullptr;
}
UCommonActivatableWidget* UUIPrimaryGameLayout_Base::PushWidgetToLayer(
FGameplayTag LayerTag,
TSubclassOf<UCommonActivatableWidget> WidgetClass)
{
if (!WidgetClass)
{
return nullptr;
}
if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag))
{
return Stack->AddWidget(WidgetClass);
}
return nullptr;
}
void UUIPrimaryGameLayout_Base::PopWidgetFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget)
{
if (!Widget)
{
return;
}
if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag))
{
Stack->RemoveWidget(*Widget);
}
}
void UUIPrimaryGameLayout_Base::ClearLayer(FGameplayTag LayerTag)
{
if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag))
{
Stack->ClearWidgets();
}
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "GameplayTagContainer.h"
#include "Templates/SubclassOf.h"
#include "UIPrimaryGameLayout_Base.generated.h"
class UCommonActivatableWidget;
class UCommonActivatableWidgetStack;
/**
* Layout raiz ("switch principal") do cliente. Mantém 4 stacks CommonUI,
* um por camada (GameplayTag UI.Layer.* — ver UI/UILayerTags.h):
*
* Stack_Game ← UI.Layer.Game (HUD de gameplay)
* Stack_GameMenu ← UI.Layer.GameMenu (menus sobre o jogo)
* Stack_Menu ← UI.Layer.Menu (front-end: Boot/Login/Lobby/…)
* Stack_Modal ← UI.Layer.Modal (diálogos/loading por cima)
*
* Camada Abstract; o WBP concreto (WBP_PrimaryGameLayout) herda DIRETO desta
* classe C++ e nomeia os 4 stacks exatamente como os BindWidget abaixo
* (ARQUITETURA.md §3.3). O UUIManagerSubsystem instancia o WBP e o adiciona
* ao viewport; o UUIFrontEndFlowSubsystem empurra telas por camada.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUIPrimaryGameLayout_Base : public UCommonUserWidget
{
GENERATED_BODY()
public:
/** Stack da camada (nullptr se a tag não for uma UI.Layer.* conhecida). */
UFUNCTION(BlueprintCallable, Category = "UI|Layout")
UCommonActivatableWidgetStack* GetLayerStack(FGameplayTag LayerTag) const;
/** Instancia e empurra a widget no topo do stack da camada. */
UFUNCTION(BlueprintCallable, Category = "UI|Layout")
UCommonActivatableWidget* PushWidgetToLayer(
FGameplayTag LayerTag,
TSubclassOf<UCommonActivatableWidget> WidgetClass);
/** Remove uma widget específica do stack da camada. */
UFUNCTION(BlueprintCallable, Category = "UI|Layout")
void PopWidgetFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget);
/** Esvazia o stack da camada. */
UFUNCTION(BlueprintCallable, Category = "UI|Layout")
void ClearLayer(FGameplayTag LayerTag);
protected:
virtual void NativeOnInitialized() override;
/** Registra um stack numa camada (chamado em NativeOnInitialized). */
void RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetStack* Stack);
// ---- Stacks por camada (o WBP concreto deve nomear assim) ----
UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> Stack_Game;
UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> Stack_GameMenu;
UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> Stack_Menu;
UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> Stack_Modal;
private:
UPROPERTY(Transient)
TMap<FGameplayTag, TObjectPtr<UCommonActivatableWidgetStack>> Layers;
};

View File

@@ -0,0 +1,143 @@
#include "UIPanel_Base.h"
#include "ZMMOThemeSubsystem.h"
#include "Engine/GameInstance.h"
#include "Components/Border.h"
#include "Components/NamedSlot.h"
#include "Styling/SlateBrush.h"
namespace
{
const FUIStyle& ResolvePanelStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
{
if (Widget)
{
if (const UGameInstance* GI = Widget->GetGameInstance())
{
if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
return Theme->GetActiveUIStyle();
}
}
}
return Fallback; // design-time: defaults Aurora Arcana
}
}
void UUIPanel_Base::RefreshUIStyle()
{
if (!Background)
{
return;
}
const FUIStyle Fallback;
const FUIStyle& AS = ResolvePanelStyle(this, Fallback);
const FUIStylePanel& P = AS.Panel;
// ---- Modo TEXTURA (padrão Hyper): brush por EUIPanelTexture ----
const FSlateBrush* BgTex = (PanelTexture != EUIPanelTexture::None)
? P.PanelBackground.Find(PanelTexture) : nullptr;
if (BgTex)
{
Background->SetBrush(*BgTex);
Background->SetPadding(bUseSmallPadding ? P.PaddingSmall : P.Padding);
if (Outline)
{
if (const FSlateBrush* OlTex = P.PanelOutline.Find(PanelTexture))
{
Outline->SetBrush(*OlTex);
Outline->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
Outline->SetVisibility(ESlateVisibility::Collapsed);
}
}
BP_ApplyPanelStyle(P);
return;
}
// ---- Modo COR (RoundedBox) ----
if (Outline)
{
Outline->SetVisibility(ESlateVisibility::Collapsed);
}
FLinearColor Fill;
FLinearColor OutlineColor = P.BorderColor;
switch (PanelType)
{
case EUIPanelType::Secondary: Fill = P.BgRaised; break;
case EUIPanelType::Tertiary: Fill = P.BgSunken; break;
case EUIPanelType::Card: Fill = P.CardSelectedBg; OutlineColor = P.CardSelectedBorder; break;
case EUIPanelType::Primary:
case EUIPanelType::None:
default: Fill = P.Bg; break;
}
FSlateBrush Brush = Background->Background;
Brush.TintColor = FSlateColor(Fill);
if (bRoundedBackground)
{
const float R = bUseSmallPadding ? P.CornerRadiusSmall : P.CornerRadius;
Brush.DrawAs = ESlateBrushDrawType::RoundedBox;
Brush.OutlineSettings = FSlateBrushOutlineSettings(
FVector4(R, R, R, R), FSlateColor(OutlineColor), P.BorderWidth);
Brush.OutlineSettings.RoundingType = ESlateBrushRoundingType::FixedRadius;
}
else
{
Brush.DrawAs = ESlateBrushDrawType::Box;
Brush.OutlineSettings = FSlateBrushOutlineSettings(
FVector4(0, 0, 0, 0), FSlateColor(OutlineColor), P.BorderWidth);
}
Background->SetBrush(Brush);
Background->SetPadding(bUseSmallPadding ? P.PaddingSmall : P.Padding);
BP_ApplyPanelStyle(P);
}
void UUIPanel_Base::NativePreConstruct()
{
Super::NativePreConstruct();
RefreshUIStyle();
}
void UUIPanel_Base::NativeConstruct()
{
Super::NativeConstruct();
if (!bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.AddDynamic(this, &UUIPanel_Base::HandleThemeChanged);
bThemeBound = true;
}
}
}
RefreshUIStyle();
}
void UUIPanel_Base::NativeDestruct()
{
if (bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.RemoveDynamic(this, &UUIPanel_Base::HandleThemeChanged);
}
}
bThemeBound = false;
}
Super::NativeDestruct();
}
void UUIPanel_Base::HandleThemeChanged(FName /*NewThemeId*/)
{
RefreshUIStyle();
}

View File

@@ -0,0 +1,77 @@
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "UI/UIStyleTypes.h"
#include "UI/UIStyleTokens.h"
#include "UIPanel_Base.generated.h"
class UBorder;
class UNamedSlot;
/**
* Painel base do ZMMO — mesmo padrão do UUIButton_Base, mas container.
* Fundação CommonUI (UCommonUserWidget). Camada Abstract; o WBP concreto
* (UI_Panel_Master) herda DIRETO desta classe C++ (UMG não encadeia árvores
* de WBP — ver ARQUITETURA.md §3.3).
*
* Visual data-driven por FUIStyle.Panel (UZMMOThemeSubsystem). Designers
* injetam o conteúdo no NamedSlot "Content".
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUIPanel_Base : public UCommonUserWidget
{
GENERATED_BODY()
public:
/** Tipo do painel — escolhe Bg/BgRaised/BgSunken/Card de FUIStylePanel. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
EUIPanelType PanelType = EUIPanelType::Primary;
/** Arredonda o Background (RoundedBox) com a borda do tema. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
bool bRoundedBackground = true;
/** Usa o padding "pequeno" do tema (PaddingSmall) em vez de Padding. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
bool bUseSmallPadding = false;
/**
* Conjunto de textura (modo Hyper). Se != None E houver brush no mapa
* FUIStylePanel.PanelBackground p/ esta chave, usa o painel TEXTURIZADO
* (brush bg + outline). Senão, cai no modo COR (RoundedBox acima).
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
EUIPanelTexture PanelTexture = EUIPanelTexture::None;
/** Re-resolve os tokens do tema ativo e reaplica no Background. */
UFUNCTION(BlueprintCallable, Category = "UI Style")
void RefreshUIStyle();
protected:
virtual void NativePreConstruct() override;
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
/** Hook opcional: o WBP pode estender/sobrescrever a aplicação visual. */
UFUNCTION(BlueprintImplementableEvent, Category = "UI Style",
meta = (DisplayName = "Apply Panel Style"))
void BP_ApplyPanelStyle(const FUIStylePanel& Panel);
/** Widgets visuais opcionais. */
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UBorder> Background;
/** Contorno texturizado por cima do fundo (modo textura, padrão Hyper). */
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UBorder> Outline;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UNamedSlot> Content;
private:
UFUNCTION()
void HandleThemeChanged(FName NewThemeId);
bool bThemeBound = false;
};

View File

@@ -17,6 +17,7 @@ public class ZMMO : ModuleRules
"Niagara", "Niagara",
"CommonUI", "CommonUI",
"CommonInput", "CommonInput",
"GameplayTags",
"ZeusNetwork" "ZeusNetwork"
}); });
@@ -33,6 +34,7 @@ public class ZMMO : ModuleRules
"ZMMO/Game/Network", "ZMMO/Game/Network",
"ZMMO/Game/UI", "ZMMO/Game/UI",
"ZMMO/Game/UI/Widgets", "ZMMO/Game/UI/Widgets",
"ZMMO/Game/UI/FrontEnd",
"ZMMO/Data", "ZMMO/Data",
"ZMMO/Data/Items", "ZMMO/Data/Items",
"ZMMO/Data/Mobs", "ZMMO/Data/Mobs",