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:
@@ -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;
|
||||
};
|
||||
|
||||
342
Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.cpp
Normal file
342
Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
105
Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.h
Normal file
105
Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.h
Normal 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;
|
||||
};
|
||||
3
Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.cpp
Normal file
3
Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.cpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "ZMMOLoginSaveGame.h"
|
||||
|
||||
const FString UZMMOLoginSaveGame::SlotName = TEXT("LoginCache");
|
||||
37
Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.h
Normal file
37
Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.h
Normal 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;
|
||||
};
|
||||
325
Source/ZMMO/Game/UI/Widgets/UIInput_Base.cpp
Normal file
325
Source/ZMMO/Game/UI/Widgets/UIInput_Base.cpp
Normal 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);
|
||||
}
|
||||
156
Source/ZMMO/Game/UI/Widgets/UIInput_Base.h
Normal file
156
Source/ZMMO/Game/UI/Widgets/UIInput_Base.h
Normal 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;
|
||||
};
|
||||
181
Source/ZMMO/Game/UI/Widgets/UILabel_Base.cpp
Normal file
181
Source/ZMMO/Game/UI/Widgets/UILabel_Base.cpp
Normal 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();
|
||||
}
|
||||
131
Source/ZMMO/Game/UI/Widgets/UILabel_Base.h
Normal file
131
Source/ZMMO/Game/UI/Widgets/UILabel_Base.h
Normal 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;
|
||||
};
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user