feat(ui): Input/Label widgets via FSlateBrush + Lembrar Acesso

- UI_Input_Master refatorado: SizeBox -> Overlay -> Background/Stroke (UImage)
  + EditableTextBox. Cor magenta default eliminada — UImages atrás
  pintam o visual via DT_UI_Styles.
- UI_Label_Master novo: wrapper "label + input" com 2 slots de UI_Input_Master
  (Stacked/Inline). Reaproveita o input puro como composição reutilizável.
- UILabel_Base/UIInput_Base (C++): hierarquia limpa, propagação de Variant/
  HintText/FontSize/PreferredWidth/Height/bIsPassword pai->filho com
  passthrough em 0/vazio (override só quando setado).
- FUIStyleInput*: campos avulsos (BackgroundColor/Texture/DrawAs/Margin)
  trocados por FSlateBrush completo — DT expõe agora Image, ImageSize,
  Matiz, Desenhar como, Ladrilhos, Margin, UV no Designer.
- 11 texturas Hyper copiadas (T_Bar_Gradient_Background_Long + 10 borders)
  e referenciadas em DT_UI_Styles row Default (Backgrounds + Strokes das 4
  variantes Box/Outline/Underline/Search).
- ZMMOThemeSubsystem.Initialize: força chamada de ResolveActiveUIStyle
  mesmo quando ActiveTheme não muda — evita ActiveUIStyle ficar vazio
  quando DefaultThemeAsset é nullptr (sintoma: Brushes não renderizavam
  em PIE/runtime).
- "Lembrar Acesso": UZMMOLoginSaveGame nova (USaveGame com SavedUsername
  + bRememberAccess), load assíncrono em NativeOnActivated, save/delete
  em HandleLoginClicked. Slot "LoginCache" em Saved/SaveGames/. Padrão
  alinhado com jogos comerciais — nunca persiste senha (CWE-256);
  refresh token + DPAPI virão quando auth real entrar.
- WBP_Login: Input_User/Password agora são UI_Label_Master_C; checkbox
  Lembrar bindada por nome (RememberAccess -> UUICheckBox_Base*).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 22:51:44 -03:00
parent 89cf2219ff
commit c9152cd27b
25 changed files with 1768 additions and 73 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -5,6 +5,7 @@
#include "Styling/SlateBrush.h"
#include "Layout/Margin.h"
#include "Templates/SubclassOf.h"
#include "Engine/Texture2D.h"
#include "UIStyleTypes.h"
#include "UIStyleTokens.generated.h"
@@ -171,7 +172,97 @@ struct ZMMO_API FUIStyleText
float TitleLetterSpacing = 0.f; // informativo (RichText/futuro)
};
/** Cores de uma variante de botão (primary/secondary/danger/ghost). */
/**
* Uma categoria tipográfica nomeada do tema. As categorias NÃO são fixas em
* C++: o autor cria as que quiser ("Title", "Section", "Body", "Label", …)
* no mapa FUIStyle.TextRoles do DT_UI_Styles. O UUICommonText_Base só
* referencia o nome (dropdown vem do DT).
*/
USTRUCT(BlueprintType)
struct ZMMO_API FUITextStyle
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Text")
FSlateFontInfo Font;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Text")
FLinearColor Color = FLinearColor(FColor(244, 240, 230));
/** Caixa-alta (ex.: labels/eyebrows). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Text")
bool bUppercase = false;
};
// =====================================================================
// FUIStyleButton — padrão multi-map (Hyper-style). 3 sub-mapas com a MESMA
// chave (nome da variante: "Primary", "Secondary", "Danger", "Ghost", ou
// customizada). UUIButton_Base.Variant é FName e busca em cada sub-mapa.
// O struct FUIStyleButtonVariant (composto, FLAT) permanece com a forma
// antiga — assim o hook BP_ApplyUIStyle dos WBP_Master continua funcional.
// =====================================================================
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButtonBackground
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgNormal = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgHover = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgPressed = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgDisabled = FLinearColor(0.f, 0.f, 0.f, 0.42f);
/** Textura opcional como background (Hyper-style). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TObjectPtr<UTexture2D> BackgroundTexture = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FMargin BackgroundMargin = FMargin(8.f);
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButtonStroke
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BorderNormal = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BorderHover = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button", meta = (ClampMin = "0"))
float BorderWidth = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button", meta = (ClampMin = "0"))
float CornerRadius = 8.f;
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButtonFont
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor TextColor = FLinearColor::White;
/** CommonTextStyle define fonte/tamanho/peso; TextColor sobrescreve a cor. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TSubclassOf<UCommonTextStyle> TextStyle;
};
/**
* View FLAT composta da variante de botão (Background + Stroke + Font).
* Mantida com a forma antiga (BgNormal/BgHover/.../TextStyle no topo) para
* NÃO QUEBRAR o hook BP_ApplyUIStyle nos WBP_Master existentes. É construída
* pelo UUIButton_Base::ResolveVariant a partir dos 3 sub-mapas do tema.
*/
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButtonVariant
{
@@ -198,14 +289,21 @@ struct ZMMO_API FUIStyleButtonVariant
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor TextColor = FLinearColor::White;
/**
* Estilo de texto desta variante (padrão Hyper/CommonUI: CommonTextStyle).
* Define fonte/tamanho/peso. A COR é sobreposta pelo TextColor acima
* (FUIStyle manda na cor por tema). Permite Primary/Secondary/Danger/Ghost
* com tipografias diferentes. Preenchido em DT_UI_Styles por tema.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TSubclassOf<UCommonTextStyle> TextStyle;
// Campos extras vindos das sub-categorias (background texture + stroke):
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button")
TObjectPtr<UTexture2D> BackgroundTexture = nullptr;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button")
FMargin BackgroundMargin = FMargin(8.f);
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button")
float BorderWidth = 1.f;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button")
float CornerRadius = 8.f;
};
USTRUCT(BlueprintType)
@@ -213,74 +311,85 @@ struct ZMMO_API FUIStyleButton
{
GENERATED_BODY()
/**
* Defaults = paleta Aurora Arcana (styles.css .btn-*). Garante que mesmo
* sem DataTable (preview de designer) o botão já saia estilizado.
*/
FUIStyleButton()
{
// .btn-primary — dourado, texto quase preto
Primary.BgNormal = FLinearColor(FColor(201, 167, 90)); // gold
Primary.BgHover = FLinearColor(FColor(231, 200, 115)); // gold-hi
Primary.BgPressed = FLinearColor(FColor(201, 167, 90));
Primary.BgDisabled = FLinearColor(FColor(201, 167, 90, 107));
Primary.BorderNormal = FLinearColor(FColor(201, 167, 90));
Primary.BorderHover = FLinearColor(FColor(231, 200, 115));
Primary.TextColor = FLinearColor(FColor(10, 10, 18)); // ~#0A0A12
// ---- Backgrounds (Primary/Secondary/Danger/Ghost) ----
FUIStyleButtonBackground PriBg;
PriBg.BgNormal = FLinearColor(FColor(201, 167, 90)); // gold
PriBg.BgHover = FLinearColor(FColor(231, 200, 115)); // gold-hi
PriBg.BgPressed = FLinearColor(FColor(201, 167, 90));
PriBg.BgDisabled = FLinearColor(FColor(201, 167, 90, 107));
Backgrounds.Add(TEXT("Primary"), PriBg);
// .btn-secondary — painel sólido + borda
Secondary.BgNormal = FLinearColor(FColor(17, 24, 42)); // panel-2
Secondary.BgHover = FLinearColor(FColor(22, 32, 58)); // #16203A
Secondary.BgPressed = FLinearColor(FColor(17, 24, 42));
Secondary.BorderNormal = FLinearColor(FColor(58, 75, 120)); // border
Secondary.BorderHover = FLinearColor(FColor(78, 163, 255)); // blue
Secondary.TextColor = FLinearColor(FColor(244, 240, 230));// text
FUIStyleButtonBackground SecBg;
SecBg.BgNormal = FLinearColor(FColor(17, 24, 42)); // panel-2
SecBg.BgHover = FLinearColor(FColor(22, 32, 58));
SecBg.BgPressed = FLinearColor(FColor(17, 24, 42));
Backgrounds.Add(TEXT("Secondary"), SecBg);
// .btn-danger
Danger.BgNormal = FLinearColor(FColor(217, 92, 92, 36)); // rgba(.14)
Danger.BgHover = FLinearColor(FColor(217, 92, 92, 66)); // rgba(.26)
Danger.BgPressed = FLinearColor(FColor(217, 92, 92, 36));
Danger.BorderNormal = FLinearColor(FColor(217, 92, 92)); // danger
Danger.BorderHover = FLinearColor(FColor(217, 92, 92));
Danger.TextColor = FLinearColor(FColor(240, 182, 182)); // #F0B6B6
FUIStyleButtonBackground DngBg;
DngBg.BgNormal = FLinearColor(FColor(217, 92, 92, 36));
DngBg.BgHover = FLinearColor(FColor(217, 92, 92, 66));
DngBg.BgPressed = FLinearColor(FColor(217, 92, 92, 36));
Backgrounds.Add(TEXT("Danger"), DngBg);
// .btn-ghost — transparente + borda suave
Ghost.BgNormal = FLinearColor::Transparent;
Ghost.BgHover = FLinearColor(FColor(22, 32, 58, 128));
Ghost.BgPressed = FLinearColor::Transparent;
Ghost.BorderNormal = FLinearColor(FColor(58, 75, 120, 140)); // border-soft
Ghost.BorderHover = FLinearColor(FColor(78, 163, 255));
Ghost.TextColor = FLinearColor(FColor(174, 182, 200)); // text-2
FUIStyleButtonBackground GhoBg;
GhoBg.BgNormal = FLinearColor::Transparent;
GhoBg.BgHover = FLinearColor(FColor(22, 32, 58, 128));
GhoBg.BgPressed = FLinearColor::Transparent;
Backgrounds.Add(TEXT("Ghost"), GhoBg);
// ---- Strokes ----
FUIStyleButtonStroke PriS;
PriS.BorderNormal = FLinearColor(FColor(201, 167, 90));
PriS.BorderHover = FLinearColor(FColor(231, 200, 115));
Strokes.Add(TEXT("Primary"), PriS);
FUIStyleButtonStroke SecS;
SecS.BorderNormal = FLinearColor(FColor(58, 75, 120));
SecS.BorderHover = FLinearColor(FColor(78, 163, 255));
Strokes.Add(TEXT("Secondary"), SecS);
FUIStyleButtonStroke DngS;
DngS.BorderNormal = FLinearColor(FColor(217, 92, 92));
DngS.BorderHover = FLinearColor(FColor(217, 92, 92));
Strokes.Add(TEXT("Danger"), DngS);
FUIStyleButtonStroke GhoS;
GhoS.BorderNormal = FLinearColor(FColor(58, 75, 120, 140));
GhoS.BorderHover = FLinearColor(FColor(78, 163, 255));
Strokes.Add(TEXT("Ghost"), GhoS);
// ---- Fonts ----
FUIStyleButtonFont PriF; PriF.TextColor = FLinearColor(FColor(10, 10, 18));
Fonts.Add(TEXT("Primary"), PriF);
FUIStyleButtonFont SecF; SecF.TextColor = FLinearColor(FColor(244, 240, 230));
Fonts.Add(TEXT("Secondary"), SecF);
FUIStyleButtonFont DngF; DngF.TextColor = FLinearColor(FColor(240, 182, 182));
Fonts.Add(TEXT("Danger"), DngF);
FUIStyleButtonFont GhoF; GhoF.TextColor = FLinearColor(FColor(174, 182, 200));
Fonts.Add(TEXT("Ghost"), GhoF);
}
// .btn-primary — dourado, texto quase preto
/** Cores/textura de fundo por variante (chave FName). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FUIStyleButtonVariant Primary;
TMap<FName, FUIStyleButtonBackground> Backgrounds;
// .btn-secondary — painel sólido + borda
/** Borda (cor, espessura, raio) por variante. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FUIStyleButtonVariant Secondary;
TMap<FName, FUIStyleButtonStroke> Strokes;
// .btn-danger
/** Tipografia (cor de texto, CommonTextStyle) por variante. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FUIStyleButtonVariant Danger;
// .btn-ghost — transparente + borda suave
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FUIStyleButtonVariant Ghost;
TMap<FName, FUIStyleButtonFont> Fonts;
// Globais comuns às variantes:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FMargin Padding = FMargin(22.f, 12.f);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
float MinHeight = 48.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
float CornerRadius = 8.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
float BorderWidth = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
float HoverScale = 1.02f;
@@ -445,33 +554,332 @@ struct ZMMO_API FUIStyleTooltip
FLinearColor TextColor = FLinearColor(FColor(244, 240, 230)); // Text
};
/** Spinner/throbber indeterminado (ex.: "Conectando..." no Boot). */
// =====================================================================
// FUIStyleSpinner — padrão multi-map (Hyper-style). Sub-mapas por categoria
// (Colors, Layouts) com a MESMA CHAVE de variante. Permite criar variantes
// como "Default", "Small", "Loading", etc.
// =====================================================================
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleSpinnerColors
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
FLinearColor Color = FLinearColor(FColor(201, 167, 90)); // Gold
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
FLinearColor TrackColor = FLinearColor(FColor(58, 75, 120, 140));
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleSpinnerLayout
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
float Radius = 28.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
float Thickness = 3.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner", meta = (ClampMin = "1"))
int32 NumberOfPieces = 8;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner", meta = (ClampMin = "0.05"))
float Period = 0.9f;
};
/** View FLAT composta da variante de Spinner. */
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleSpinnerVariant
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Spinner")
FUIStyleSpinnerColors Colors;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Spinner")
FUIStyleSpinnerLayout Layout;
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleSpinner
{
GENERATED_BODY()
/** Cor das peças do spinner. Default = Gold (Aurora Arcana). */
FUIStyleSpinner()
{
Colors.Add(TEXT("Default"), FUIStyleSpinnerColors());
Layouts.Add(TEXT("Default"), FUIStyleSpinnerLayout());
}
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
FLinearColor Color = FLinearColor(FColor(201, 167, 90)); // Gold
TMap<FName, FUIStyleSpinnerColors> Colors;
/** Cor do "trilho"/peças apagadas (UCircularThrobber não usa; reservado). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
FLinearColor TrackColor = FLinearColor(FColor(58, 75, 120, 140));
TMap<FName, FUIStyleSpinnerLayout> Layouts;
};
/** Raio do anel (px). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
float Radius = 28.f;
// =====================================================================
// FUIStyleCheckBox — padrão multi-map (Hyper-style). Sub-mapas por categoria
// visual (Fills, Strokes, Layouts) com a MESMA CHAVE de variante. Cada
// UUICheckBox_Base referencia o nome (FName Variant) e o C++ compõe.
// =====================================================================
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleCheckBoxFill
{
GENERATED_BODY()
/** Espessura da peça (px). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
float Thickness = 3.f;
/** Preenchimento da caixa quando marcada. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
FLinearColor BoxColor = FLinearColor(FColor(201, 167, 90)); // Gold
/** Número de peças do UCircularThrobber. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner", meta = (ClampMin = "1"))
int32 NumberOfPieces = 8;
/** Cor do "tique"/glifo de check. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
FLinearColor CheckColor = FLinearColor(FColor(18, 22, 34)); // Bg0
/** Período de uma volta (s). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner", meta = (ClampMin = "0.05"))
float Period = 0.9f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
FLinearColor HoveredColor = FLinearColor(FColor(224, 196, 130));
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
FLinearColor PressedColor = FLinearColor(FColor(178, 146, 74));
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
FLinearColor DisabledColor = FLinearColor(FColor(90, 96, 112, 140));
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleCheckBoxStroke
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
FLinearColor BorderColor = FLinearColor(FColor(58, 75, 120, 140));
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox", meta = (ClampMin = "0"))
float BorderThickness = 1.5f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox", meta = (ClampMin = "0"))
float CornerRadius = 4.f;
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleCheckBoxLayout
{
GENERATED_BODY()
/** Lado da caixa (px). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox", meta = (ClampMin = "8"))
float BoxSize = 22.f;
};
/** View FLAT composta da variante de CheckBox — usada pelo BP hook. */
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleCheckBoxVariant
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "UI Style|CheckBox")
FUIStyleCheckBoxFill Fill;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|CheckBox")
FUIStyleCheckBoxStroke Stroke;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|CheckBox")
FUIStyleCheckBoxLayout Layout;
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleCheckBox
{
GENERATED_BODY()
FUIStyleCheckBox()
{
Fills.Add(TEXT("Default"), FUIStyleCheckBoxFill());
Strokes.Add(TEXT("Default"), FUIStyleCheckBoxStroke());
Layouts.Add(TEXT("Default"), FUIStyleCheckBoxLayout());
}
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
TMap<FName, FUIStyleCheckBoxFill> Fills;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
TMap<FName, FUIStyleCheckBoxStroke> Strokes;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox")
TMap<FName, FUIStyleCheckBoxLayout> Layouts;
};
// =====================================================================
// FUIStyleInput — padrão multi-map (estilo Hyper): sub-mapas independentes
// por categoria visual (Backgrounds, Strokes, Typography). O widget escolhe
// um nome único de variante (ex.: "Box") que existe em CADA sub-mapa;
// reaproveita-se sem dor (ex.: mesma Stroke em vários Backgrounds).
// =====================================================================
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleInputBackground
{
GENERATED_BODY()
/**
* Brush completo do background (Image, Image Size, Matiz, Desenhar como,
* Ladrilhos, Margin/9-slice, UV). Mesma UI de FSlateBrush exposta no UMG.
* Se Brush.ResourceObject for nulo, pinta cor sólida (procedural — DrawAs
* = Image sem textura).
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
FSlateBrush Brush;
/** Tint multiplicado por cima do Brush em estado de hover. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
FLinearColor HoveredColor = FLinearColor::White;
/** Tint multiplicado por cima do Brush em estado de focus. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
FLinearColor FocusedColor = FLinearColor::White;
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleInputStroke
{
GENERATED_BODY()
/**
* Brush completo da borda (Image, Image Size, Matiz, Desenhar como,
* Ladrilhos, Margin). Use textura 9-slice (DrawAs=Box) ou Image para
* stroke decorada estilo Hyper Search.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
FSlateBrush Brush;
/** Espessura procedural (efeito visual; usado por widgets que renderizam stroke vector). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input", meta = (ClampMin = "0"))
float BorderThickness = 1.f;
/** Raio do canto procedural (efeito visual; usado por widgets que renderizam RoundedBox). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input", meta = (ClampMin = "0"))
float CornerRadius = 6.f;
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleInputTypography
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
FLinearColor TextColor = FLinearColor(FColor(244, 240, 230));
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
FLinearColor HintColor = FLinearColor(FColor(150, 160, 180, 200));
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input", meta = (ClampMin = "1"))
float Size = 16.f;
/** Fonte específica desta tipografia. Se vazia, usa FUIStyle.Text.BodyFont. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
FSlateFontInfo FontOverride;
};
/**
* View "composta" da variante (Background + Stroke + Typography). Não é
* editada diretamente no DT — é o que o UUIInput_Base monta ao resolver
* a chave Variant nos 3 sub-mapas. Mantida aqui para uso no C++.
*/
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleInputVariant
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Input")
FUIStyleInputBackground Background;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Input")
FUIStyleInputStroke Stroke;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Input")
FUIStyleInputTypography Typography;
};
/**
* Campo de texto — padrão multi-map (Hyper-style). 3 sub-mapas independentes
* com a MESMA CHAVE (nome da variante: "Box", "Outline", "Underline",
* "Search", ou customizada). O UUIInput_Base usa Variant=FName e busca o
* pedaço em cada sub-mapa; assim dá pra reaproveitar (ex.: a mesma Stroke
* em variantes com Backgrounds diferentes).
*
* Construtor já popula com defaults bonitos inspirados na Login.
*/
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleInput
{
GENERATED_BODY()
FUIStyleInput()
{
auto MakeBg = [](const FLinearColor& Tint)
{
FUIStyleInputBackground B;
B.Brush.TintColor = FSlateColor(Tint);
B.Brush.DrawAs = ESlateBrushDrawType::Image;
return B;
};
// ---- Backgrounds ----
Backgrounds.Add(TEXT("Box"), MakeBg(FLinearColor(FColor(9, 13, 24, 235))));
Backgrounds.Add(TEXT("Outline"), MakeBg(FLinearColor(0, 0, 0, 0)));
Backgrounds.Add(TEXT("Underline"), MakeBg(FLinearColor(0, 0, 0, 0)));
Backgrounds.Add(TEXT("Search"), MakeBg(FLinearColor(FColor(17, 24, 42))));
auto MakeStroke = [](const FLinearColor& Tint, float Thickness, float Radius)
{
FUIStyleInputStroke S;
S.Brush.TintColor = FSlateColor(Tint);
S.Brush.DrawAs = ESlateBrushDrawType::Box;
S.Brush.Margin = FMargin(4.f);
S.BorderThickness = Thickness;
S.CornerRadius = Radius;
return S;
};
// ---- Strokes ----
Strokes.Add(TEXT("Box"), MakeStroke(FLinearColor(FColor(58, 75, 120, 140)), 1.f, 6.f));
Strokes.Add(TEXT("Outline"), MakeStroke(FLinearColor(FColor(58, 75, 120)), 1.f, 6.f));
Strokes.Add(TEXT("Underline"), MakeStroke(FLinearColor(FColor(58, 75, 120, 160)), 2.f, 0.f));
Strokes.Add(TEXT("Search"), MakeStroke(FLinearColor(FColor(58, 75, 120, 140)), 1.f, 18.f));
// ---- Typography ----
FUIStyleInputTypography DefaultType;
DefaultType.TextColor = FLinearColor(FColor(244, 240, 230));
DefaultType.HintColor = FLinearColor(FColor(150, 160, 180, 200));
DefaultType.Size = 16.f;
Typography.Add(TEXT("Box"), DefaultType);
Typography.Add(TEXT("Outline"), DefaultType);
Typography.Add(TEXT("Underline"), DefaultType);
Typography.Add(TEXT("Search"), DefaultType);
}
/** Cores/textura de fundo por variante (chave FName). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
TMap<FName, FUIStyleInputBackground> Backgrounds;
/** Borda (cor, espessura, arredondamento) por variante. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
TMap<FName, FUIStyleInputStroke> Strokes;
/** Tipografia (cor de texto/hint, tamanho, fonte) por variante. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input")
TMap<FName, FUIStyleInputTypography> Typography;
/** Padding interno do campo (px, uniforme) — comum a todas variantes. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input", meta = (ClampMin = "0"))
float Padding = 10.f;
/** Altura mínima do campo (px). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input", meta = (ClampMin = "0"))
float MinHeight = 44.f;
/** Largura mínima PADRÃO do campo (px). Designer mostra esse tamanho. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input", meta = (ClampMin = "0"))
float MinWidth = 240.f;
};

View File

@@ -0,0 +1,342 @@
#include "UILoginScreen_Base.h"
#include "ZMMO.h"
#include "CharServerOpcodes.h"
#include "UIButton_Base.h"
#include "UICheckBox_Base.h"
#include "UILabel_Base.h"
#include "ZMMOLoginSaveGame.h"
#include "Kismet/GameplayStatics.h"
#include "UIFrontEndFlowSubsystem.h"
#include "ZMMOThemeSubsystem.h"
#include "ZeusCharServerSubsystem.h"
#include "ZeusPacketWriter.h"
#include "ZeusPacketReader.h"
#include "CommonTextBlock.h"
#include "Components/Border.h"
#include "Engine/GameInstance.h"
#include "Engine/DataTable.h"
#include "UI/UIStyleRow.h"
namespace
{
// Mesmo helper da Boot: runtime usa o tema ativo; design-time carrega a
// row "Default" de DT_UI_Styles para o Designer ver o fundo.
FUIStyle ResolveLoginStyle(const UUserWidget* 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: editar o DT_UI_Styles e
// RECOMPILAR o WBP já reflete (sem reiniciar o editor).
FUIStyle DesignStyle;
bool bHas = false;
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
{
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
FName(TEXT("Default")), TEXT("UILoginDesign"), false))
{
DesignStyle = Row->Style;
bHas = true;
}
}
return bHas ? DesignStyle : Fallback;
}
// Token de DEV: o StubTokenValidator aceita `dev:<numero>` ou `dev:<uuid>`.
// Um nome de usuário comum NÃO valida — então em dev mapeamos para
// `dev:<numero>` (o número digitado, se houver; senão 1). Auth real
// (HTTP /auth/login + JWT) entra em tarefa dedicada.
FString MakeDevToken(const FString& User)
{
const FString Trimmed = User.TrimStartAndEnd();
if (!Trimmed.IsEmpty() && Trimmed.IsNumeric())
{
return FString::Printf(TEXT("dev:%s"), *Trimmed);
}
return TEXT("dev:1");
}
}
void UUILoginScreen_Base::NativePreConstruct()
{
Super::NativePreConstruct();
RefreshUIStyle(); // preview do fundo no Designer (DT_UI_Styles "Default")
}
void UUILoginScreen_Base::NativeOnActivated()
{
Super::NativeOnActivated(); // base: RefreshUIStyle + BP_OnScreenActivated
if (Btn_Login)
{
Btn_Login->OnClicked().AddUObject(this, &UUILoginScreen_Base::HandleLoginClicked);
}
if (Btn_Back)
{
Btn_Back->OnClicked().AddUObject(this, &UUILoginScreen_Base::HandleBackClicked);
}
UZeusCharServerSubsystem* Char = GetCharServer();
if (Char && !bRawBound)
{
Char->OnRawMessage.AddDynamic(this, &UUILoginScreen_Base::HandleCharRawMessage);
bRawBound = true;
}
bAwaitingAuth = false;
const bool bConnected = Char && Char->IsConnected();
SetBusy(false);
if (Btn_Login)
{
Btn_Login->SetIsInteractionEnabled(bConnected);
}
SetStatus(bConnected ? IdleText : NotConnectedText);
// "Lembrar Acesso": tenta restaurar usuário salvo (async, não trava UI).
LoadRememberedAccess();
}
void UUILoginScreen_Base::NativeOnDeactivated()
{
if (Btn_Login)
{
Btn_Login->OnClicked().RemoveAll(this);
}
if (Btn_Back)
{
Btn_Back->OnClicked().RemoveAll(this);
}
if (bRawBound)
{
if (UZeusCharServerSubsystem* Char = GetCharServer())
{
Char->OnRawMessage.RemoveDynamic(this, &UUILoginScreen_Base::HandleCharRawMessage);
}
bRawBound = false;
}
Super::NativeOnDeactivated();
}
void UUILoginScreen_Base::RefreshUIStyle_Implementation()
{
Super::RefreshUIStyle_Implementation();
const FUIStyle Fallback;
const FUIStyle AS = ResolveLoginStyle(this, Fallback);
if (Border_Bg)
{
// Fundo data-driven (DT_UI_Styles) — sem hard-ref de textura no WBP
// (§5). Fallback p/ cor se vazio.
if (AS.ScreenBackground.GetResourceObject() != nullptr)
{
Border_Bg->SetBrush(AS.ScreenBackground);
}
else
{
Border_Bg->SetBrushColor(AS.Palette.Bg0);
}
}
if (Text_Status)
{
Text_Status->SetColorAndOpacity(FSlateColor(AS.Semantic.OnSurfaceMuted));
}
}
UZeusCharServerSubsystem* UUILoginScreen_Base::GetCharServer() const
{
const UGameInstance* GI = GetGameInstance();
return GI ? GI->GetSubsystem<UZeusCharServerSubsystem>() : nullptr;
}
void UUILoginScreen_Base::SetBusy(bool bBusy)
{
if (Btn_Login)
{
Btn_Login->SetIsInteractionEnabled(!bBusy);
}
if (Input_User)
{
Input_User->SetIsEnabled(!bBusy);
}
if (Input_Password)
{
Input_Password->SetIsEnabled(!bBusy);
}
}
void UUILoginScreen_Base::SetStatus(const FText& InText)
{
if (Text_Status)
{
Text_Status->SetText(InText);
}
}
void UUILoginScreen_Base::HandleLoginClicked()
{
if (bAwaitingAuth)
{
return;
}
UZeusCharServerSubsystem* Char = GetCharServer();
if (!Char || !Char->IsConnected())
{
SetStatus(NotConnectedText);
return;
}
const FString User = Input_User ? Input_User->GetText().ToString() : FString();
const FString Pass = Input_Password ? Input_Password->GetText().ToString() : FString();
if (User.TrimStartAndEnd().IsEmpty() || Pass.IsEmpty())
{
SetStatus(EmptyFieldsText);
return;
}
// "Lembrar Acesso": salva/apaga ANTES do envio do auth — atende a
// expectativa "marquei → vai lembrar próxima vez" independente do
// resultado da autenticação (senha errada ainda lembra do usuário,
// padrão dos launchers comerciais).
SaveOrClearRememberedAccess(User);
// Escopo dev: token aceito pelo StubTokenValidator (senha ignorada no
// stub; mantida na UI para o fluxo real futuro).
const FString Token = MakeDevToken(User);
FZeusPacketWriter W;
W.WriteStringUtf8(Token);
const bool bSent = Char->SendCharRequest(
ZMMOCharOp::C_CHAR_AUTH_REQUEST, W.GetBuffer());
if (!bSent)
{
UE_LOG(LogZMMO, Warning, TEXT("Login: falha ao enviar C_CHAR_AUTH_REQUEST."));
SetStatus(NotConnectedText);
return;
}
bAwaitingAuth = true;
SetBusy(true);
SetStatus(AuthenticatingText);
UE_LOG(LogZMMO, Log, TEXT("Login: C_CHAR_AUTH_REQUEST enviado (token dev)."));
}
void UUILoginScreen_Base::HandleBackClicked()
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
{
Flow->RequestBack();
}
}
}
void UUILoginScreen_Base::HandleCharRawMessage(int32 Opcode, const TArray<uint8>& Payload)
{
if (!bAwaitingAuth)
{
return; // só nos interessa a resposta do auth que disparamos
}
if (Opcode == ZMMOCharOp::S_CHAR_AUTH_OK)
{
bAwaitingAuth = false;
FZeusPacketReader R(Payload.GetData(), Payload.Num());
FString AccountId, Username;
R.ReadStringUtf8(AccountId);
R.ReadStringUtf8(Username);
uint64 ExpiresMs = 0;
R.ReadUInt64(ExpiresMs);
UE_LOG(LogZMMO, Log, TEXT("Login: S_CHAR_AUTH_OK (account=%s user=%s) → ServerSelect."),
*AccountId, *Username);
if (const UGameInstance* GI = GetGameInstance())
{
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
{
Flow->RequestEnterServerSelect();
}
}
}
else if (Opcode == ZMMOCharOp::S_CHAR_AUTH_REJECT)
{
bAwaitingAuth = false;
FZeusPacketReader R(Payload.GetData(), Payload.Num());
uint16 Reason = 0;
R.ReadUInt16(Reason);
UE_LOG(LogZMMO, Warning, TEXT("Login: S_CHAR_AUTH_REJECT (reason=%u)."), Reason);
SetBusy(false);
SetStatus(FText::Format(
NSLOCTEXT("ZMMO", "LoginRejectFmt", "{0} (cód. {1})"),
RejectedText, FText::AsNumber(Reason)));
}
}
void UUILoginScreen_Base::LoadRememberedAccess()
{
if (!UGameplayStatics::DoesSaveGameExist(
UZMMOLoginSaveGame::SlotName, UZMMOLoginSaveGame::UserIndex))
{
return;
}
FAsyncLoadGameFromSlotDelegate Delegate;
Delegate.BindUFunction(this,
FName(TEXT("HandleLoadLoginCacheCompleted")));
UGameplayStatics::AsyncLoadGameFromSlot(
UZMMOLoginSaveGame::SlotName, UZMMOLoginSaveGame::UserIndex, Delegate);
}
void UUILoginScreen_Base::HandleLoadLoginCacheCompleted(const FString& /*InSlotName*/,
int32 /*InUserIndex*/, USaveGame* LoadedGame)
{
const UZMMOLoginSaveGame* Save = Cast<UZMMOLoginSaveGame>(LoadedGame);
if (!Save || !Save->bRememberAccess || Save->SavedUsername.IsEmpty())
{
return;
}
if (Input_User)
{
Input_User->SetText(FText::FromString(Save->SavedUsername));
}
if (RememberAccess)
{
RememberAccess->SetIsChecked(true);
}
}
void UUILoginScreen_Base::SaveOrClearRememberedAccess(const FString& User)
{
const bool bRemember = RememberAccess ? RememberAccess->IsChecked() : false;
if (bRemember)
{
UZMMOLoginSaveGame* Save = Cast<UZMMOLoginSaveGame>(
UGameplayStatics::CreateSaveGameObject(UZMMOLoginSaveGame::StaticClass()));
if (Save)
{
Save->SavedUsername = User.TrimStartAndEnd();
Save->bRememberAccess = true;
UGameplayStatics::AsyncSaveGameToSlot(
Save, UZMMOLoginSaveGame::SlotName, UZMMOLoginSaveGame::UserIndex);
}
}
else if (UGameplayStatics::DoesSaveGameExist(
UZMMOLoginSaveGame::SlotName, UZMMOLoginSaveGame::UserIndex))
{
// Checkbox desmarcada: apaga save antigo (não vamos lembrar mais).
UGameplayStatics::DeleteGameInSlot(
UZMMOLoginSaveGame::SlotName, UZMMOLoginSaveGame::UserIndex);
}
}

View File

@@ -0,0 +1,105 @@
#pragma once
#include "CoreMinimal.h"
#include "UIActivatableScreen_Base.h"
#include "UILoginScreen_Base.generated.h"
class UUIButton_Base;
class UUICheckBox_Base;
class UUILabel_Base;
class UCommonTextBlock;
class UBorder;
class USaveGame;
class UZeusCharServerSubsystem;
/**
* Tela de Login — reconstruída a partir do mock do Zeus UMG Forge
* (WBP_LoginScreen) com os widgets compartilhados (UI_Button_Master,
* UI_CheckBox_Master). Camada Abstract; o WBP concreto (WBP_Login) herda
* DIRETO desta classe (ARQUITETURA.md §3.3).
*
* Auth (escopo atual — dev): ao clicar "Entrar", envia C_CHAR_AUTH_REQUEST
* (opcode 2000) pelo WebSocket do UZeusCharServerSubsystem com um token
* `dev:<n>` (StubTokenValidator aceita em dev; auth real virá em tarefa
* dedicada). S_CHAR_AUTH_OK → Flow::RequestEnterServerSelect; reject →
* mensagem de erro. Sem hard-ref de asset de tema (§5).
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUILoginScreen_Base : public UUIActivatableScreen_Base
{
GENERATED_BODY()
protected:
virtual void NativePreConstruct() override;
virtual void NativeOnActivated() override;
virtual void NativeOnDeactivated() override;
virtual void RefreshUIStyle_Implementation() override;
/** Textos de status/feedback da autenticação. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText IdleText = FText::FromString(TEXT("Acesso protegido pelo Farol da Manhã."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText AuthenticatingText = FText::FromString(TEXT("Autenticando…"));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText NotConnectedText = FText::FromString(TEXT("Sem conexão com o Gateway."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText EmptyFieldsText = FText::FromString(TEXT("Informe usuário e senha."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText RejectedText = FText::FromString(TEXT("Acesso negado pelo Gateway."));
// ---- Widgets do WBP (nomes esperados) ----
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UBorder> Border_Bg;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UUILabel_Base> Input_User;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UUILabel_Base> Input_Password;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UUICheckBox_Base> RememberAccess;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UUIButton_Base> Btn_Login;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UUIButton_Base> Btn_Back;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Text_Status;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Text_Version;
private:
UZeusCharServerSubsystem* GetCharServer() const;
void SetBusy(bool bBusy);
void SetStatus(const FText& InText);
void HandleLoginClicked();
void HandleBackClicked();
UFUNCTION()
void HandleCharRawMessage(int32 Opcode, const TArray<uint8>& Payload);
/** "Lembrar Acesso" — async load do cache local (LoginCache slot) e
popula Input_User + Check_Remember se houver entrada salva. */
void LoadRememberedAccess();
UFUNCTION()
void HandleLoadLoginCacheCompleted(const FString& InSlotName, int32 InUserIndex, USaveGame* LoadedGame);
/** Salva (se checkbox marcada) ou apaga (se desmarcada) o cache de Login.
Chamado em HandleLoginClicked — antes do envio do auth, atende a
expectativa "marquei → vai lembrar próxima vez" independente do
resultado da autenticação. */
void SaveOrClearRememberedAccess(const FString& User);
bool bRawBound = false;
bool bAwaitingAuth = false;
};

View File

@@ -0,0 +1,3 @@
#include "ZMMOLoginSaveGame.h"
const FString UZMMOLoginSaveGame::SlotName = TEXT("LoginCache");

View File

@@ -0,0 +1,37 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/SaveGame.h"
#include "ZMMOLoginSaveGame.generated.h"
/**
* Cache local da tela de Login. Persiste APENAS o nome do usuário (texto
* plano — risco zero, é só rótulo) e o estado da checkbox "Lembrar Acesso".
*
* NÃO persistir senha aqui (CWE-256). Quando o auth real (HTTP/JWT) entrar,
* adicionar `FString EncryptedRefreshToken` cifrado via DPAPI
* (CryptProtectData no Windows / equivalente em outras plataformas) — o token
* é opaco e revogável, padrão dominante em jogos comerciais
* (Battle.net/Riot/Epic). Senha em si nunca toca o disco do cliente.
*
* Arquivo: [Project]/Saved/SaveGames/LoginCache_0.sav (binary blob das
* UPROPERTY não-Transient via UE serializer).
*/
UCLASS()
class ZMMO_API UZMMOLoginSaveGame : public USaveGame
{
GENERATED_BODY()
public:
UPROPERTY()
FString SavedUsername;
UPROPERTY()
bool bRememberAccess = false;
/** Slot do arquivo .sav (sem extensão). */
static const FString SlotName;
/** Index de usuário do SO — 0 para single-player local. */
static constexpr int32 UserIndex = 0;
};

View File

@@ -0,0 +1,325 @@
#include "UIInput_Base.h"
#include "ZMMOThemeSubsystem.h"
#include "Engine/GameInstance.h"
#include "Engine/DataTable.h"
#include "Components/EditableTextBox.h"
#include "Components/SizeBox.h"
#include "Components/Image.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateBrush.h"
#include "UI/UIStyleRow.h"
namespace
{
// Mesmo helper das outras bases: runtime usa o tema ativo; design-time
// carrega a row "Default" de DT_UI_Styles para o Designer ver igual.
FUIStyle ResolveInputStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
{
if (Widget)
{
if (const UGameInstance* GI = Widget->GetGameInstance())
{
if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
return Theme->GetActiveUIStyle();
}
}
}
FUIStyle DesignStyle;
bool bHas = false;
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
{
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
FName(TEXT("Default")), TEXT("UIInputDesign"), false))
{
DesignStyle = Row->Style;
bHas = true;
}
}
return bHas ? DesignStyle : Fallback;
}
// Pinta um UImage com o FSlateBrush vindo do DT (já tem Image, ImageSize,
// TintColor, DrawAs, Tiling, Margin/9-slice, UV — tudo). Brush vazio
// (sem ResourceObject e DrawAs!=NoDrawType) força Image procedural com
// Tint da própria struct — evita brush nulo que vira magenta.
void PaintImage(UImage* Img, const FSlateBrush& Brush)
{
if (!IsValid(Img)) { return; }
FSlateBrush B = Brush;
if (!B.GetResourceObject() && B.DrawAs != ESlateBrushDrawType::NoDrawType)
{
// Sem textura: pintura procedural (cor sólida).
B.DrawAs = ESlateBrushDrawType::Image;
}
Img->SetBrush(B);
// SetColorAndOpacity = identidade; cor já vem do Brush.TintColor.
Img->SetColorAndOpacity(FLinearColor::White);
}
}
TArray<FString> UUIInput_Base::CollectVariantOptions()
{
TArray<FString> Options;
for (const TCHAR* N : { TEXT("Box"), TEXT("Outline"), TEXT("Underline"), TEXT("Search") })
{
Options.Add(N);
}
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
{
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
FName(TEXT("Default")), TEXT("UIInputVariantOptions"), false))
{
for (const TPair<FName, FUIStyleInputBackground>& P : Row->Style.Input.Backgrounds) { Options.AddUnique(P.Key.ToString()); }
for (const TPair<FName, FUIStyleInputStroke>& P : Row->Style.Input.Strokes) { Options.AddUnique(P.Key.ToString()); }
for (const TPair<FName, FUIStyleInputTypography>& P : Row->Style.Input.Typography) { Options.AddUnique(P.Key.ToString()); }
}
}
return Options;
}
TArray<FString> UUIInput_Base::GetVariantOptions() const
{
return CollectVariantOptions();
}
FUIStyleInputVariant UUIInput_Base::ResolveVariant(const FUIStyleInput& I) const
{
// Padrão multi-map (Hyper-style): compõe a variante buscando o nome
// (Variant FName) em CADA sub-mapa independente. Se um sub-mapa não
// tiver a chave, cai nos Defaults inline (sempre GC-safe — só
// cores/floats no constructor de FUIStyleInput).
static const FUIStyleInput Defaults;
FUIStyleInputVariant V;
if (const FUIStyleInputBackground* E = I.Backgrounds.Find(Variant)) { V.Background = *E; }
else if (const FUIStyleInputBackground* E2 = Defaults.Backgrounds.Find(Variant)) { V.Background = *E2; }
else { V.Background = Defaults.Backgrounds.FindRef(TEXT("Box")); }
if (const FUIStyleInputStroke* E = I.Strokes.Find(Variant)) { V.Stroke = *E; }
else if (const FUIStyleInputStroke* E2 = Defaults.Strokes.Find(Variant)) { V.Stroke = *E2; }
else { V.Stroke = Defaults.Strokes.FindRef(TEXT("Box")); }
if (const FUIStyleInputTypography* E = I.Typography.Find(Variant)) { V.Typography = *E; }
else if (const FUIStyleInputTypography* E2 = Defaults.Typography.Find(Variant)) { V.Typography = *E2; }
else { V.Typography = Defaults.Typography.FindRef(TEXT("Box")); }
return V;
}
void UUIInput_Base::ApplyStyle(const FUIStyleInputVariant& V, float InPadding,
const FSlateFontInfo& ThemeFont, float FontSizePx)
{
// Background e Stroke (UImages) — seguro em design-time e runtime.
if (IsValid(Background))
{
PaintImage(Background, V.Background.Brush);
}
if (IsValid(Stroke))
{
PaintImage(Stroke, V.Stroke.Brush);
}
if (!IsValid(Input))
{
return;
}
// IMPORTANTE: NÃO mexer no UEditableTextBox em design-time. Qualquer
// chamada (SetForegroundColor / SetIsPassword / SetHintText / SetWidgetStyle)
// dispara SEditableText::SynchronizeTextStyle → DetermineFont via FAttribute
// delegate dangling → crash de "FSlateFontInfo::FSlateFontInfo() reading
// 0xff..." nesta build da engine. Em design-time só pintamos Background/
// Stroke (UImages, path estável). Preview do HintText/Password só em PIE.
if (IsDesignTime())
{
return;
}
// SÓ chamadas pontuais — NÃO chamar SetWidgetStyle (copia FEditableTextBoxStyle
// inteiro incluindo TextStyle.Font). DetermineFont callback durante Prepass
// do Slate dereferencia FSlateFontInfo dangling se a Font veio do DT com
// FontObject UObject que ficou em estado intermediário → crash em
// FSlateFontInfo::FSlateFontInfo() durante SEditableText::SynchronizeTextStyle.
// Background brushes do EditableTextBox: configurar transparente direto no
// asset UI_Input_Master no Designer (one-time setup) — o Background/Stroke
// (UImages) atrás do EditableTextBox é quem pinta o visual via DT.
Input->SetForegroundColor(V.Typography.TextColor);
Input->SetIsPassword(bIsPassword);
if (!HintText.IsEmpty())
{
Input->SetHintText(HintText);
}
// Suprime warnings de parâmetros não usados (assinatura mantida pra
// compatibilidade — InPadding/ThemeFont/FontSizePx podem ser usados
// futuramente quando o caminho de SetWidgetStyle for re-habilitado de
// forma segura).
(void)InPadding;
(void)ThemeFont;
(void)FontSizePx;
}
void UUIInput_Base::RefreshUIStyle()
{
const FUIStyle Fallback;
const FUIStyle AS = ResolveInputStyle(this, Fallback);
const FUIStyleInput& I = AS.Input;
const FUIStyleInputVariant V = ResolveVariant(I);
// Tamanho via Width/Height Override (mais agressivo que MinDesired — força
// tamanho exato). Resolução em 3 camadas:
// 1. PreferredWidth/Height UPROPERTY > 0 → override explícito (designer)
// 2. User mexeu DIRETO no Size_Root.WidthOverride (Designer) → preserva
// 3. Senão → aplica do DT (I.MinWidth/MinHeight)
if (IsValid(Size_Root))
{
auto ApplyDimension = [](USizeBox* Box, bool bIsWidth, float PreferredVal,
float ThemeVal, float& LastThemeApplied)
{
const bool bHasManualFlag = bIsWidth ? Box->IsWidthOverride() : Box->IsHeightOverride();
const float CurrentVal = bIsWidth ? Box->GetWidthOverride() : Box->GetHeightOverride();
auto Setter = [Box, bIsWidth](float V)
{
if (bIsWidth) { Box->SetWidthOverride(V); }
else { Box->SetHeightOverride(V); }
};
if (PreferredVal > 0.f)
{
// (1) Override explícito via UPROPERTY.
Setter(PreferredVal);
LastThemeApplied = -1.f; // user controla via UPROPERTY a partir daqui
}
else if (bHasManualFlag && LastThemeApplied >= 0.f && CurrentVal != LastThemeApplied)
{
// (2) User editou direto no SizeBox no Designer — preserva.
}
else if (bHasManualFlag && LastThemeApplied < 0.f)
{
// (2b) Flag manual ativo mas C++ nunca aplicou nesta instância
// (fresh load): assume que é override do user no asset — preserva.
}
else
{
// (3) Aplica do DT e marca como "C++ aplicou".
Setter(ThemeVal);
LastThemeApplied = ThemeVal;
}
};
ApplyDimension(Size_Root, /*bIsWidth=*/true, PreferredWidth, I.MinWidth, LastThemeWidthApplied);
ApplyDimension(Size_Root, /*bIsWidth=*/false, PreferredHeight, I.MinHeight, LastThemeHeightApplied);
}
// Size: 0 na instância → usa V.Typography.Size (do DT da variante).
const float ChosenSize = (FontSize > 0.f) ? FontSize : V.Typography.Size;
// Font: V.Typography.FontOverride se válida; senão FUIStyle.Text.BodyFont.
const FSlateFontInfo& ThemeFont = V.Typography.FontOverride.HasValidFont()
? V.Typography.FontOverride : AS.Text.BodyFont;
ApplyStyle(V, I.Padding, ThemeFont, ChosenSize);
BP_ApplyInputStyle(I);
}
FText UUIInput_Base::GetText() const
{
return IsValid(Input) ? Input->GetText() : FText::GetEmpty();
}
void UUIInput_Base::SetText(FText InText)
{
if (IsValid(Input))
{
Input->SetText(InText);
}
}
void UUIInput_Base::SetHintTextRuntime(FText InHint)
{
HintText = InHint;
if (IsValid(Input)) { Input->SetHintText(InHint); }
}
void UUIInput_Base::SetIsPasswordRuntime(bool bInIsPassword)
{
bIsPassword = bInIsPassword;
if (IsValid(Input)) { Input->SetIsPassword(bInIsPassword); }
}
void UUIInput_Base::NativePreConstruct()
{
Super::NativePreConstruct();
RefreshUIStyle();
}
void UUIInput_Base::SynchronizeProperties()
{
Super::SynchronizeProperties();
RefreshUIStyle();
}
void UUIInput_Base::NativeConstruct()
{
Super::NativeConstruct();
if (!bInputBound && IsValid(Input))
{
Input->OnTextChanged.AddDynamic(this, &UUIInput_Base::HandleTextChanged);
Input->OnTextCommitted.AddDynamic(this, &UUIInput_Base::HandleTextCommitted);
bInputBound = true;
}
if (!bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.AddDynamic(this, &UUIInput_Base::HandleThemeChanged);
bThemeBound = true;
}
}
}
RefreshUIStyle();
}
void UUIInput_Base::NativeDestruct()
{
if (bInputBound && IsValid(Input))
{
Input->OnTextChanged.RemoveDynamic(this, &UUIInput_Base::HandleTextChanged);
Input->OnTextCommitted.RemoveDynamic(this, &UUIInput_Base::HandleTextCommitted);
bInputBound = false;
}
if (bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.RemoveDynamic(this, &UUIInput_Base::HandleThemeChanged);
}
}
bThemeBound = false;
}
Super::NativeDestruct();
}
void UUIInput_Base::HandleThemeChanged(FName /*NewThemeId*/)
{
RefreshUIStyle();
}
void UUIInput_Base::HandleTextChanged(const FText& Text)
{
OnTextChanged.Broadcast(Text);
}
void UUIInput_Base::HandleTextCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
OnTextCommitted.Broadcast(Text, CommitMethod);
}

View File

@@ -0,0 +1,156 @@
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "UI/UIStyleTokens.h"
#include "UI/UIStyleTypes.h"
#include "UIInput_Base.generated.h"
class UEditableTextBox;
class USizeBox;
class UImage;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUIInputTextChanged, const FText&, Text);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FUIInputTextCommitted, const FText&, Text, ETextCommit::Type, CommitMethod);
/**
* Campo de texto SÓ-INPUT (sem label). Padrão visual inspirado no UI_Search do
* Hyper: SizeBox raiz → Overlay → Background (UImage) + Stroke (UImage) +
* Input (UEditableTextBox). Os UImages recebem a aparência (cor ou textura
* 9-slice via DT_UI_Styles); o EditableTextBox fica transparente e só
* recebe fonte/cor de texto — assim a cor magenta default (brush vazio) é
* eliminada.
*
* Variantes visuais são data-driven via FUIStyle.Input (Backgrounds/Strokes/
* Typography) — chave FName (Box/Outline/Underline/Search/custom). Para
* compor com rótulo + layout (Stacked/Inline), use UUILabel_Base.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUIInput_Base : public UCommonUserWidget
{
GENERATED_BODY()
public:
/** Re-resolve os tokens do tema ativo e reaplica. */
UFUNCTION(BlueprintCallable, Category = "UI Style")
void RefreshUIStyle();
UFUNCTION(BlueprintPure, Category = "Input")
FText GetText() const;
UFUNCTION(BlueprintCallable, Category = "Input")
void SetText(FText InText);
UFUNCTION(BlueprintCallable, Category = "Input")
void SetHintTextRuntime(FText InHint);
UFUNCTION(BlueprintCallable, Category = "Input")
void SetIsPasswordRuntime(bool bInIsPassword);
UPROPERTY(BlueprintAssignable, Category = "Input")
FUIInputTextChanged OnTextChanged;
UPROPERTY(BlueprintAssignable, Category = "Input")
FUIInputTextCommitted OnTextCommitted;
/**
* Tamanho da fonte do campo. 0 = usa o padrão do DT_UI_Styles
* (FUIStyleInputTypography.Size). > 0 = sobrescreve aqui no Designer.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input", meta = (ClampMin = "0"))
float FontSize = 0.f;
/** Placeholder exibido quando vazio. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
FText HintText;
/**
* Variante visual do input (dropdown vem de FUIStyle.Input.Variants no
* DT_UI_Styles + as 4 clássicas como fallback). Data-driven, sem enum.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input",
meta = (GetOptions = "GetVariantOptions"))
FName Variant = TEXT("Box");
/** Opções do dropdown de Variant (data-driven do DT_UI_Styles). */
UFUNCTION()
TArray<FString> GetVariantOptions() const;
/** Helper compartilhado p/ UUILabel_Base (mesma lista de variantes). */
static TArray<FString> CollectVariantOptions();
/**
* Largura preferida do campo (px). 0 = usa o padrão do DT
* (FUIStyle.Input.MinWidth). > 0 = sobrescreve aqui no Designer.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input|Size", meta = (ClampMin = "0"))
float PreferredWidth = 0.f;
/**
* Altura preferida do campo (px). 0 = usa o padrão do DT
* (FUIStyle.Input.MinHeight). > 0 = sobrescreve aqui no Designer.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input|Size", meta = (ClampMin = "0"))
float PreferredHeight = 0.f;
/** Campo de senha (oculta os caracteres). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
bool bIsPassword = false;
protected:
virtual void NativePreConstruct() override;
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
virtual void SynchronizeProperties() override;
/** Hook opcional: o WBP pode refinar além do que o C++ aplica. */
UFUNCTION(BlueprintImplementableEvent, Category = "UI Style",
meta = (DisplayName = "Apply Input Style"))
void BP_ApplyInputStyle(const FUIStyleInput& InputStyle);
// SizeBox raiz (controla PreferredWidth/MinHeight). Opcional: se o WBP
// não tiver, fallback fica ileso (sem set de tamanho).
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<USizeBox> Size_Root;
// Background pintável (cor + textura 9-slice opcional).
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UImage> Background;
// Stroke / borda (cor + textura 9-slice opcional via FUIStyleInputStroke).
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UImage> Stroke;
// Campo de texto cru. Fica transparente — Background/Stroke pintam atrás.
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UEditableTextBox> Input;
private:
FUIStyleInputVariant ResolveVariant(const FUIStyleInput& I) const;
void ApplyStyle(const FUIStyleInputVariant& V, float InPadding,
const FSlateFontInfo& ThemeFont, float FontSizePx);
UFUNCTION()
void HandleThemeChanged(FName NewThemeId);
UFUNCTION()
void HandleTextChanged(const FText& Text);
UFUNCTION()
void HandleTextCommitted(const FText& Text, ETextCommit::Type CommitMethod);
bool bThemeBound = false;
bool bInputBound = false;
/**
* Último valor que o C++ aplicou no Size_Root->WidthOverride a partir do
* tema. Se o valor atual do SizeBox for diferente, presume-se que o user
* editou manualmente (no Designer) — C++ deixa intacto. -1 = não aplicado
* (próxima chamada aplica do tema).
*/
UPROPERTY(Transient)
float LastThemeWidthApplied = -1.f;
UPROPERTY(Transient)
float LastThemeHeightApplied = -1.f;
};

View File

@@ -0,0 +1,181 @@
#include "UILabel_Base.h"
#include "UIInput_Base.h"
#include "UICommonText_Base.h"
#include "Components/VerticalBox.h"
#include "Components/HorizontalBox.h"
#include "CommonTextBlock.h"
UUIInput_Base* UUILabel_Base::ActiveInput() const
{
if (LabelLayout == EUIInputLabelLayout::Inline)
{
return IsValid(Input_Inline) ? Input_Inline : Input_Stacked;
}
return IsValid(Input_Stacked) ? Input_Stacked : Input_Inline;
}
void UUILabel_Base::ApplyLayout()
{
// Só alterna VISIBILIDADE (nunca muta a árvore — isso crasha em
// construção/thumbnail do editor).
const bool bInline = (LabelLayout == EUIInputLabelLayout::Inline);
if (IsValid(Box_Stacked))
{
Box_Stacked->SetVisibility(bInline
? ESlateVisibility::Collapsed : ESlateVisibility::SelfHitTestInvisible);
}
if (IsValid(Row_Inline))
{
Row_Inline->SetVisibility(bInline
? ESlateVisibility::SelfHitTestInvisible : ESlateVisibility::Collapsed);
}
}
void UUILabel_Base::PropagateInputProps()
{
auto Apply = [this](UUIInput_Base* In)
{
if (!IsValid(In)) { return; }
// Variant: controle de aparência do UILabel_Base — sempre propaga.
In->Variant = Variant;
// bIsPassword: sempre propaga (controle explícito).
In->bIsPassword = bIsPassword;
// Overrides "0/vazio = passthrough": só sobrescreve se o UILabel_Base
// definiu valor explícito; assim o user pode setar PreferredWidth/Height,
// FontSize, HintText DIRETO no UI_Input_Master filho e o pai não zera.
if (FontSize > 0.f) { In->FontSize = FontSize; }
if (!HintText.IsEmpty()) { In->HintText = HintText; }
if (PreferredWidth > 0.f) { In->PreferredWidth = PreferredWidth; }
if (PreferredHeight > 0.f) { In->PreferredHeight = PreferredHeight; }
In->RefreshUIStyle();
};
Apply(Input_Stacked);
Apply(Input_Inline);
}
TArray<FString> UUILabel_Base::GetVariantOptions() const
{
return UUIInput_Base::CollectVariantOptions();
}
TArray<FString> UUILabel_Base::GetLabelTextRoleOptions() const
{
return UUICommonText_Base::GetThemeTextRoleOptions();
}
void UUILabel_Base::RefreshUIStyle()
{
auto SetupLabel = [this](UCommonTextBlock* L)
{
if (!L) { return; }
if (!LabelText.IsEmpty()) { L->SetText(LabelText); }
if (UUICommonText_Base* CT = Cast<UUICommonText_Base>(L))
{
if (CT->TextRole != LabelTextRole) { CT->TextRole = LabelTextRole; }
CT->RefreshUIStyle();
}
};
SetupLabel(Label_Stacked);
SetupLabel(Label_Inline);
PropagateInputProps();
}
FText UUILabel_Base::GetText() const
{
const UUIInput_Base* In = ActiveInput();
return In ? In->GetText() : FText::GetEmpty();
}
void UUILabel_Base::SetText(FText InText)
{
if (UUIInput_Base* In = ActiveInput())
{
In->SetText(InText);
}
}
void UUILabel_Base::SetHintTextRuntime(FText InHint)
{
HintText = InHint;
if (IsValid(Input_Stacked)) { Input_Stacked->SetHintTextRuntime(InHint); }
if (IsValid(Input_Inline)) { Input_Inline->SetHintTextRuntime(InHint); }
}
void UUILabel_Base::SetIsPasswordRuntime(bool bInIsPassword)
{
bIsPassword = bInIsPassword;
if (IsValid(Input_Stacked)) { Input_Stacked->SetIsPasswordRuntime(bInIsPassword); }
if (IsValid(Input_Inline)) { Input_Inline->SetIsPasswordRuntime(bInIsPassword); }
}
void UUILabel_Base::BindInputDelegates()
{
if (bInputBound) { return; }
if (IsValid(Input_Stacked))
{
Input_Stacked->OnTextChanged.AddDynamic(this, &UUILabel_Base::HandleChildTextChanged);
Input_Stacked->OnTextCommitted.AddDynamic(this, &UUILabel_Base::HandleChildTextCommitted);
}
if (IsValid(Input_Inline))
{
Input_Inline->OnTextChanged.AddDynamic(this, &UUILabel_Base::HandleChildTextChanged);
Input_Inline->OnTextCommitted.AddDynamic(this, &UUILabel_Base::HandleChildTextCommitted);
}
bInputBound = true;
}
void UUILabel_Base::UnbindInputDelegates()
{
if (!bInputBound) { return; }
if (IsValid(Input_Stacked))
{
Input_Stacked->OnTextChanged.RemoveDynamic(this, &UUILabel_Base::HandleChildTextChanged);
Input_Stacked->OnTextCommitted.RemoveDynamic(this, &UUILabel_Base::HandleChildTextCommitted);
}
if (IsValid(Input_Inline))
{
Input_Inline->OnTextChanged.RemoveDynamic(this, &UUILabel_Base::HandleChildTextChanged);
Input_Inline->OnTextCommitted.RemoveDynamic(this, &UUILabel_Base::HandleChildTextCommitted);
}
bInputBound = false;
}
void UUILabel_Base::HandleChildTextChanged(const FText& Text)
{
OnTextChanged.Broadcast(Text);
}
void UUILabel_Base::HandleChildTextCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
OnTextCommitted.Broadcast(Text, CommitMethod);
}
void UUILabel_Base::NativePreConstruct()
{
Super::NativePreConstruct();
ApplyLayout();
RefreshUIStyle();
}
void UUILabel_Base::SynchronizeProperties()
{
Super::SynchronizeProperties();
ApplyLayout();
RefreshUIStyle();
}
void UUILabel_Base::NativeConstruct()
{
Super::NativeConstruct();
BindInputDelegates();
ApplyLayout();
RefreshUIStyle();
}
void UUILabel_Base::NativeDestruct()
{
UnbindInputDelegates();
Super::NativeDestruct();
}

View File

@@ -0,0 +1,131 @@
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "UI/UIStyleTokens.h"
#include "UI/UIStyleTypes.h"
#include "UILabel_Base.generated.h"
class UUIInput_Base;
class UCommonTextBlock;
class UVerticalBox;
class UHorizontalBox;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUILabelTextChanged, const FText&, Text);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FUILabelTextCommitted, const FText&, Text, ETextCommit::Type, CommitMethod);
/**
* Composição "label + input" — dois arranjos pré-montados (Box_Stacked,
* Row_Inline) onde cada Input_* é uma instância concreta de UI_Input_Master
* (UUIInput_Base). Alterna VISIBILIDADE entre layouts (Stacked vs Inline);
* nunca muta a árvore (mutar em construção/thumbnail crasha o editor).
*
* Encaminha a API de texto pro input ativo. Variant/HintText/FontSize/
* PreferredWidth/bIsPassword são propagados pros DOIS filhos (preview
* correto em qualquer LabelLayout).
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUILabel_Base : public UCommonUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "UI Style")
void RefreshUIStyle();
UFUNCTION(BlueprintPure, Category = "Input")
FText GetText() const;
UFUNCTION(BlueprintCallable, Category = "Input")
void SetText(FText InText);
UFUNCTION(BlueprintCallable, Category = "Input")
void SetHintTextRuntime(FText InHint);
UFUNCTION(BlueprintCallable, Category = "Input")
void SetIsPasswordRuntime(bool bInIsPassword);
UPROPERTY(BlueprintAssignable, Category = "Input")
FUILabelTextChanged OnTextChanged;
UPROPERTY(BlueprintAssignable, Category = "Input")
FUILabelTextCommitted OnTextCommitted;
// ---- Rótulo ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Label")
FText LabelText;
/** Categoria tipográfica do rótulo (dropdown vem do DT_UI_Styles). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Label",
meta = (GetOptions = "GetLabelTextRoleOptions"))
FName LabelTextRole = TEXT("Label");
UFUNCTION()
TArray<FString> GetLabelTextRoleOptions() const;
/** Posição do rótulo (label acima x label à esquerda). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Label")
EUIInputLabelLayout LabelLayout = EUIInputLabelLayout::Stacked;
// ---- Encaminhado pro input ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input",
meta = (GetOptions = "GetVariantOptions"))
FName Variant = TEXT("Box");
UFUNCTION()
TArray<FString> GetVariantOptions() const;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
FText HintText;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input", meta = (ClampMin = "0"))
float FontSize = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input|Size", meta = (ClampMin = "0"))
float PreferredWidth = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input|Size", meta = (ClampMin = "0"))
float PreferredHeight = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
bool bIsPassword = false;
protected:
virtual void NativePreConstruct() override;
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
virtual void SynchronizeProperties() override;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UVerticalBox> Box_Stacked;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UHorizontalBox> Row_Inline;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Label_Stacked;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UUIInput_Base> Input_Stacked;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Label_Inline;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UUIInput_Base> Input_Inline;
private:
UUIInput_Base* ActiveInput() const;
void ApplyLayout();
void PropagateInputProps();
void BindInputDelegates();
void UnbindInputDelegates();
UFUNCTION()
void HandleChildTextChanged(const FText& Text);
UFUNCTION()
void HandleChildTextCommitted(const FText& Text, ETextCommit::Type CommitMethod);
bool bInputBound = false;
};

View File

@@ -27,6 +27,13 @@ void UZMMOThemeSubsystem::Initialize(FSubsystemCollectionBase& Collection)
}
ResolveActiveTheme();
// Garantia: se ResolveActiveTheme não disparou ResolveActiveUIStyle (porque
// ActiveTheme não mudou — caso comum quando DefaultTheme é nullptr e nada
// resolveu), força a carga do DT_UI_Styles agora para não deixar
// ActiveUIStyle como FUIStyle() default (brushes vazios → nada renderiza
// em runtime). O fallback interno acha a linha "Default" do DT.
ResolveActiveUIStyle(ActiveThemeId);
}
void UZMMOThemeSubsystem::Deinitialize()