diff --git a/Content/ZMMO/Data/UI/DT_UI_Styles.uasset b/Content/ZMMO/Data/UI/DT_UI_Styles.uasset index 4ae3003..2406533 100644 Binary files a/Content/ZMMO/Data/UI/DT_UI_Styles.uasset and b/Content/ZMMO/Data/UI/DT_UI_Styles.uasset differ diff --git a/Content/ZMMO/UI/FrontEnd/WBP_Login.uasset b/Content/ZMMO/UI/FrontEnd/WBP_Login.uasset new file mode 100644 index 0000000..1d64da3 Binary files /dev/null and b/Content/ZMMO/UI/FrontEnd/WBP_Login.uasset differ diff --git a/Content/ZMMO/UI/Shared/UI_Input_Master.uasset b/Content/ZMMO/UI/Shared/UI_Input_Master.uasset new file mode 100644 index 0000000..5232bbd Binary files /dev/null and b/Content/ZMMO/UI/Shared/UI_Input_Master.uasset differ diff --git a/Content/ZMMO/UI/Shared/UI_Label_Master.uasset b/Content/ZMMO/UI/Shared/UI_Label_Master.uasset new file mode 100644 index 0000000..9f25a0e Binary files /dev/null and b/Content/ZMMO/UI/Shared/UI_Label_Master.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/Bars/T_Bar_Gradient_Background_Long.uasset b/Content/ZMMO/UI/Textures/Hyper/Bars/T_Bar_Gradient_Background_Long.uasset new file mode 100644 index 0000000..397e6d4 Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/Bars/T_Bar_Gradient_Background_Long.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Line-512-10.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Line-512-10.uasset new file mode 100644 index 0000000..a6b2aef Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Line-512-10.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Rounded_Corner_border_white.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Rounded_Corner_border_white.uasset new file mode 100644 index 0000000..e6c42de Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Rounded_Corner_border_white.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Rounded_Corner_white.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Rounded_Corner_white.uasset new file mode 100644 index 0000000..26fd61d Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Rounded_Corner_white.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Square_FullWhite.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Square_FullWhite.uasset new file mode 100644 index 0000000..ddf4984 Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_Square_FullWhite.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_long_2048-512.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_long_2048-512.uasset new file mode 100644 index 0000000..15f1a3f Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_long_2048-512.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_long_4096-512.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_long_4096-512.uasset new file mode 100644 index 0000000..8af5f78 Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_long_4096-512.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_rectangle_1024-512.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_rectangle_1024-512.uasset new file mode 100644 index 0000000..624c43c Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_rectangle_1024-512.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_square.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_square.uasset new file mode 100644 index 0000000..2a29099 Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_square.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_thick_glow_2048-1280.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_thick_glow_2048-1280.uasset new file mode 100644 index 0000000..18f8aff Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_thick_glow_2048-1280.uasset differ diff --git a/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_thin_2048-1280.uasset b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_thin_2048-1280.uasset new file mode 100644 index 0000000..44cda40 Binary files /dev/null and b/Content/ZMMO/UI/Textures/Hyper/UI_Borders/T_border_frame_thin_2048-1280.uasset differ diff --git a/Source/ZMMO/Data/UI/UIStyleTokens.h b/Source/ZMMO/Data/UI/UIStyleTokens.h index 137ddaf..68ce067 100644 --- a/Source/ZMMO/Data/UI/UIStyleTokens.h +++ b/Source/ZMMO/Data/UI/UIStyleTokens.h @@ -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 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 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 TextStyle; + + // Campos extras vindos das sub-categorias (background texture + stroke): + UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button") + TObjectPtr 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 Backgrounds; - // .btn-secondary — painel sólido + borda + /** Borda (cor, espessura, raio) por variante. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button") - FUIStyleButtonVariant Secondary; + TMap 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 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 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 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 Fills; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox") + TMap Strokes; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|CheckBox") + TMap 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 Backgrounds; + + /** Borda (cor, espessura, arredondamento) por variante. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input") + TMap Strokes; + + /** Tipografia (cor de texto/hint, tamanho, fonte) por variante. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Input") + TMap 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; }; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.cpp b/Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.cpp new file mode 100644 index 0000000..b337f4f --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.cpp @@ -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()) + { + 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(nullptr, + TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles"))) + { + if (const FUIStyleRow* Row = DT->FindRow( + FName(TEXT("Default")), TEXT("UILoginDesign"), false)) + { + DesignStyle = Row->Style; + bHas = true; + } + } + return bHas ? DesignStyle : Fallback; + } + + // Token de DEV: o StubTokenValidator aceita `dev:` ou `dev:`. + // Um nome de usuário comum NÃO valida — então em dev mapeamos para + // `dev:` (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() : 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()) + { + Flow->RequestBack(); + } + } +} + +void UUILoginScreen_Base::HandleCharRawMessage(int32 Opcode, const TArray& 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()) + { + 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(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( + 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); + } +} diff --git a/Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.h b/Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.h new file mode 100644 index 0000000..37a4d95 --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UILoginScreen_Base.h @@ -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:` (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 Border_Bg; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Input_User; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Input_Password; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr RememberAccess; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Btn_Login; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Btn_Back; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Text_Status; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr 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& 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; +}; diff --git a/Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.cpp b/Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.cpp new file mode 100644 index 0000000..cad1be3 --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.cpp @@ -0,0 +1,3 @@ +#include "ZMMOLoginSaveGame.h" + +const FString UZMMOLoginSaveGame::SlotName = TEXT("LoginCache"); diff --git a/Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.h b/Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.h new file mode 100644 index 0000000..9cdab81 --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/ZMMOLoginSaveGame.h @@ -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; +}; diff --git a/Source/ZMMO/Game/UI/Widgets/UIInput_Base.cpp b/Source/ZMMO/Game/UI/Widgets/UIInput_Base.cpp new file mode 100644 index 0000000..4a3a4a6 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIInput_Base.cpp @@ -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()) + { + return Theme->GetActiveUIStyle(); + } + } + } + FUIStyle DesignStyle; + bool bHas = false; + if (const UDataTable* DT = LoadObject(nullptr, + TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles"))) + { + if (const FUIStyleRow* Row = DT->FindRow( + 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 UUIInput_Base::CollectVariantOptions() +{ + TArray Options; + for (const TCHAR* N : { TEXT("Box"), TEXT("Outline"), TEXT("Underline"), TEXT("Search") }) + { + Options.Add(N); + } + if (const UDataTable* DT = LoadObject(nullptr, + TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles"))) + { + if (const FUIStyleRow* Row = DT->FindRow( + FName(TEXT("Default")), TEXT("UIInputVariantOptions"), false)) + { + for (const TPair& P : Row->Style.Input.Backgrounds) { Options.AddUnique(P.Key.ToString()); } + for (const TPair& P : Row->Style.Input.Strokes) { Options.AddUnique(P.Key.ToString()); } + for (const TPair& P : Row->Style.Input.Typography) { Options.AddUnique(P.Key.ToString()); } + } + } + return Options; +} + +TArray 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()) + { + 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()) + { + 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); +} diff --git a/Source/ZMMO/Game/UI/Widgets/UIInput_Base.h b/Source/ZMMO/Game/UI/Widgets/UIInput_Base.h new file mode 100644 index 0000000..cdd51a4 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIInput_Base.h @@ -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 GetVariantOptions() const; + + /** Helper compartilhado p/ UUILabel_Base (mesma lista de variantes). */ + static TArray 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 Size_Root; + + // Background pintável (cor + textura 9-slice opcional). + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Background; + + // Stroke / borda (cor + textura 9-slice opcional via FUIStyleInputStroke). + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Stroke; + + // Campo de texto cru. Fica transparente — Background/Stroke pintam atrás. + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr 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; +}; diff --git a/Source/ZMMO/Game/UI/Widgets/UILabel_Base.cpp b/Source/ZMMO/Game/UI/Widgets/UILabel_Base.cpp new file mode 100644 index 0000000..077f30f --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UILabel_Base.cpp @@ -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 UUILabel_Base::GetVariantOptions() const +{ + return UUIInput_Base::CollectVariantOptions(); +} + +TArray 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(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(); +} diff --git a/Source/ZMMO/Game/UI/Widgets/UILabel_Base.h b/Source/ZMMO/Game/UI/Widgets/UILabel_Base.h new file mode 100644 index 0000000..1373036 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UILabel_Base.h @@ -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 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 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 Box_Stacked; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Row_Inline; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Label_Stacked; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Input_Stacked; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Label_Inline; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr 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; +}; diff --git a/Source/ZMMO/Game/UI/ZMMOThemeSubsystem.cpp b/Source/ZMMO/Game/UI/ZMMOThemeSubsystem.cpp index 6803d72..de29915 100644 --- a/Source/ZMMO/Game/UI/ZMMOThemeSubsystem.cpp +++ b/Source/ZMMO/Game/UI/ZMMOThemeSubsystem.cpp @@ -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()