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:
Binary file not shown.
BIN
Content/ZMMO/UI/FrontEnd/WBP_Login.uasset
Normal file
BIN
Content/ZMMO/UI/FrontEnd/WBP_Login.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Shared/UI_Input_Master.uasset
Normal file
BIN
Content/ZMMO/UI/Shared/UI_Input_Master.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Shared/UI_Label_Master.uasset
Normal file
BIN
Content/ZMMO/UI/Shared/UI_Label_Master.uasset
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Line-512-10.uasset
Normal file
BIN
Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Line-512-10.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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