Files
ZMMO/Source/ZMMO/Game/UI/Widgets/UICommonText_Base.cpp
Mateus Rodrigues 166650671f feat(frontend): Fase 1 + 1.5 — ServerSelect dinamico + Lobby (chars por mundo)
ServerSelect (Fase 1):
- UIServerSelectScreen_Base: subscribe OnRawMessage + C_WORLD_LIST_REQUEST
- WBP_ServerCard instanciado dinamicamente em GridPanel CardContainer (2 cols)
- UIServerCard_Base: SetFromEntry + OnCardPressed delegate + UI_Button_Master
- Push real-time S_WORLD_STATUS_UPDATE (opcode 2062) atualiza card in-place
- FZMMOWorldEntry struct, EZMMOWorldState enum no wire (offline/online/maint)
- Sem hardcap de slots — cards usam toda largura via ColumnFill weights

Lobby/CharSelect (Fase 1.5):
- UIUserLobbyScreen_Base + WBP_UserLobby (WidgetSwitcher: lista + create)
- C_CHAR_LIST_REQUEST filtrado por SelectedWorldId do FlowSubsystem
- UICharCard_Base + WBP_CharCard com Select/Delete + Accept/Cancel quando
  delete agendado; Text_DeleteCountdown atualiza em tempo real (timer 1s)
- UICharacterCreatePage_Base + WBP_CharacterCreate (Name + ComboBox class)
- Handlers S_CHAR_SELECT_OK (handoff parse), CHAR_CREATE_OK/REJECT,
  CHAR_DELETE_ACK/ACCEPT_ACK/CANCEL_ACK (refresh lista)

Auxiliares + fixes:
- ARQUITETURA_SERVER_SELECT.md + ARQUITETURA_CHARACTER_MODEL.md
- UIFrontEndFlowSubsystem: SelectedWorldId state + transicao Lobby
- DA_FrontEndScreenSet: Lobby -> WBP_UserLobby_C
- UICheckBox_Base + UICommonText_Base (componentes auxiliares)
- BSB_Button_Transparent.Disabled DrawAs=IMAGE -> NoDraw (fix bg branco)
- WBP_ServerCard layout: HBox para chips Region/Ping/Status, SizeBox 10x10
  no Dot verde (anti-overflow); SelfHitTestInvisible em containers (hover)
2026-05-21 20:06:23 -03:00

139 lines
4.4 KiB
C++

#include "UICommonText_Base.h"
#include "ZMMOThemeSubsystem.h"
#include "Engine/GameInstance.h"
#include "Engine/DataTable.h"
#include "Styling/CoreStyle.h"
#include "UI/UIStyleRow.h"
namespace
{
const TCHAR* GStylesDT = TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles");
// Retorna POR VALOR (cópia fresca): nunca persiste ponteiro de FontObject
// num static não-rastreado por GC (causa AV ao hashear a fonte no preview).
FUIStyle ResolveTextStyle(const UWidget* Widget, const FUIStyle& Fallback)
{
if (Widget)
{
if (const UGameInstance* GI = Widget->GetGameInstance())
{
if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
return Theme->GetActiveUIStyle();
}
}
}
// Design-time relê o DT a cada chamada. Local (não-static): a fonte
// fica viva apenas durante o RefreshUIStyle (o UObject UFont é mantido
// pelo DataTable carregado — sem ponteiro pendurado entre frames).
FUIStyle DesignStyle;
bool bHas = false;
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr, GStylesDT))
{
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
FName(TEXT("Default")), TEXT("UITextDesign"), false))
{
DesignStyle = Row->Style;
bHas = true;
}
}
return bHas ? DesignStyle : Fallback;
}
// Fallback: categorias clássicas a partir de FUIStyle.Text (já configurado
// com as FF_ fonts no DT) enquanto o mapa TextRoles não for preenchido.
bool LegacyRole(const FUIStyle& AS, FName Role, FSlateFontInfo& OutFont,
FLinearColor& OutColor, bool& bOutUpper)
{
const FUIStyleText& T = AS.Text;
const FUIStylePalette& P = AS.Palette;
const FString R = Role.ToString();
bOutUpper = false;
if (R.Equals(TEXT("Title"), ESearchCase::IgnoreCase)) { OutFont = T.TitleFont; OutColor = P.Text; return true; }
if (R.Equals(TEXT("Section"), ESearchCase::IgnoreCase)) { OutFont = T.SectionFont; OutColor = P.Text; return true; }
if (R.Equals(TEXT("Button"), ESearchCase::IgnoreCase)) { OutFont = T.ButtonFont; OutColor = P.Text; return true; }
if (R.Equals(TEXT("Label"), ESearchCase::IgnoreCase)) { OutFont = T.LabelFont; OutColor = P.Text2; bOutUpper = T.bLabelUppercase; return true; }
if (R.Equals(TEXT("Dim"), ESearchCase::IgnoreCase)) { OutFont = T.BodyFont; OutColor = P.TextDim; return true; }
if (R.Equals(TEXT("Body"), ESearchCase::IgnoreCase)) { OutFont = T.BodyFont; OutColor = P.Text; return true; }
return false;
}
}
void UUICommonText_Base::RefreshUIStyle()
{
const FUIStyle Fallback;
const FUIStyle AS = ResolveTextStyle(this, Fallback);
FSlateFontInfo RoleFont;
FLinearColor RoleColor = AS.Palette.Text;
bool bUpper = false;
// 1) Mapa data-driven (autor define no DT_UI_Styles).
if (const FUITextStyle* E = AS.TextRoles.Find(TextRole))
{
RoleFont = E->Font;
RoleColor = E->Color;
bUpper = E->bUppercase;
}
// 2) Fallback p/ categorias clássicas de FUIStyle.Text.
else if (!LegacyRole(AS, TextRole, RoleFont, RoleColor, bUpper))
{
RoleFont = AS.Text.BodyFont;
RoleColor = AS.Palette.Text;
}
// Nunca deixa sem fonte (evita o "A BASIC LATIN" gigante/quebrado).
if (RoleFont.FontObject == nullptr && RoleFont.CompositeFont == nullptr)
{
RoleFont = FCoreStyle::GetDefaultFontStyle("Regular", 14);
}
SetFont(RoleFont);
SetColorAndOpacity(FSlateColor(RoleColor));
if (bUpper)
{
const FText Cur = GetText();
if (!Cur.IsEmpty())
{
SetText(FText::FromString(Cur.ToString().ToUpper()));
}
}
}
TArray<FString> UUICommonText_Base::GetTextRoleOptions() const
{
return GetThemeTextRoleOptions();
}
TArray<FString> UUICommonText_Base::GetThemeTextRoleOptions()
{
TArray<FString> Options;
// Categorias clássicas sempre disponíveis (fallback garantido).
for (const TCHAR* N : { TEXT("Title"), TEXT("Section"), TEXT("Body"),
TEXT("Button"), TEXT("Label"), TEXT("Dim") })
{
Options.Add(N);
}
// + chaves definidas pelo autor no DT_UI_Styles (data-driven).
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr, GStylesDT))
{
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
FName(TEXT("Default")), TEXT("UITextRoleOptions"), false))
{
for (const TPair<FName, FUITextStyle>& Pair : Row->Style.TextRoles)
{
Options.AddUnique(Pair.Key.ToString());
}
}
}
return Options;
}
void UUICommonText_Base::SynchronizeProperties()
{
Super::SynchronizeProperties();
RefreshUIStyle();
}