feat(ui): porte 1:1 do UI_Button_Master (Hyper) para C++ auto-configuravel

UUIButton_Base enriquecido com a logica do UI_Button_Master do Hyper, lida via MCP hyperpro e convertida para C++ (Struct_Style_Button do Hyper substituido pela camada FUIStyle por tema): SetButtonText/SetNumberText/SetIcon/ApplySizeBox/ApplyInputActionStyle/InitializeButton + timeout (delegates OnButtonActivatedByTimeout/OnButtonTimerUpdated). UPROPERTYs editaveis (Button/Text/Number/Icon/Size/InputActionStyle/Timer) auto-aplicados em NativePreConstruct/NativeConstruct. BindWidgetOptional: SizeBox_Master/ButtonText(CommonTextBlock)/Number_Text/Image_Icon/Bg. InputActionWidget acessado por GetWidgetFromName (privado no UCommonButtonBase). Compila+linka OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 21:58:10 -03:00
parent 4ea5131475
commit 91b080aa68
2 changed files with 329 additions and 22 deletions

View File

@@ -2,6 +2,15 @@
#include "ZMMOThemeSubsystem.h" #include "ZMMOThemeSubsystem.h"
#include "Engine/GameInstance.h" #include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "Components/Border.h"
#include "Components/TextBlock.h"
#include "Components/Image.h"
#include "Components/SizeBox.h"
#include "Components/OverlaySlot.h"
#include "CommonTextBlock.h"
#include "CommonActionWidget.h"
namespace namespace
{ {
@@ -34,17 +43,144 @@ const FUIStyleButtonVariant& UUIButton_Base::ResolveVariant(const FUIStyleButton
} }
} }
// ---- Hyper "Set Button Text" ----
void UUIButton_Base::SetButtonText(FText InText)
{
if (!ButtonText)
{
return;
}
const FText Final = bUppercaseText
? FText::FromString(InText.ToString().ToUpper())
: InText;
ButtonText->SetText(Final);
}
// ---- Hyper "Set Number Text" ----
void UUIButton_Base::SetNumberText()
{
if (!Number_Text)
{
return;
}
if (bShowNumberText)
{
Number_Text->SetText(FText::AsNumber(NumberTextAmount));
Number_Text->SetVisibility(ESlateVisibility::Visible);
}
else
{
Number_Text->SetVisibility(ESlateVisibility::Hidden);
}
}
// ---- Hyper "Set Icon" ----
void UUIButton_Base::SetIcon()
{
if (bUseIconInsteadOfText)
{
if (Image_Icon)
{
if (IconImage)
{
Image_Icon->SetBrushResourceObject(IconImage);
}
Image_Icon->SetVisibility(ESlateVisibility::Visible);
}
if (ButtonText)
{
ButtonText->SetVisibility(ESlateVisibility::Collapsed);
}
}
else
{
if (Image_Icon)
{
Image_Icon->SetVisibility(ESlateVisibility::Collapsed);
}
if (ButtonText)
{
ButtonText->SetVisibility(ESlateVisibility::Visible);
}
}
}
// ---- Hyper "Set Sizebox Master Dimmensions" ----
void UUIButton_Base::ApplySizeBox()
{
if (bDoNotOverrideWithSizebox || !SizeBox_Master)
{
return;
}
SizeBox_Master->SetHeightOverride(InHeightOverride);
SizeBox_Master->SetWidthOverride(InWidthOverride);
}
// ---- Hyper "Set InputAction Style" ----
void UUIButton_Base::ApplyInputActionStyle()
{
// InputActionWidget é privado em UCommonButtonBase (bind interno). Pega por
// nome — o widget do WBP chama-se "InputActionWidget" (bind do CommonUI).
UCommonActionWidget* IAW = Cast<UCommonActionWidget>(
GetWidgetFromName(TEXT("InputActionWidget")));
if (!IAW)
{
return;
}
if (bShowInputActionWidget)
{
IAW->SetVisibility(ESlateVisibility::Visible);
IAW->SetIsEnabled(true);
}
else
{
IAW->SetVisibility(ESlateVisibility::Collapsed);
IAW->SetIsEnabled(false);
}
if (UOverlaySlot* OS = Cast<UOverlaySlot>(IAW->Slot))
{
OS->SetPadding(InputPadding);
OS->SetHorizontalAlignment(InputHorzAlignment);
OS->SetVerticalAlignment(InputVertAlignment);
}
}
// ---- Hyper "Initialize Button" (orquestrador) ----
void UUIButton_Base::InitializeButton()
{
SetButtonText(ButtonTextValue);
SetNumberText();
SetIcon();
ApplySizeBox();
RefreshUIStyle();
}
// ---- Hyper "Update Button Style" / "Set Brush" → nossa camada FUIStyle ----
void UUIButton_Base::RefreshUIStyle() void UUIButton_Base::RefreshUIStyle()
{ {
const FUIStyle Fallback; // defaults do struct = paleta Aurora Arcana const FUIStyle Fallback; // defaults do struct = paleta Aurora Arcana
const FUIStyle& ActiveStyle = ResolveActiveStyle(this, Fallback); const FUIStyle& ActiveStyle = ResolveActiveStyle(this, Fallback);
BP_ApplyUIStyle(ActiveStyle.Button, ResolveVariant(ActiveStyle.Button)); const FUIStyleButtonVariant& VariantStyle = ResolveVariant(ActiveStyle.Button);
if (Bg)
{
Bg->SetBrushColor(VariantStyle.BgNormal);
}
if (ButtonText)
{
ButtonText->SetColorAndOpacity(FSlateColor(VariantStyle.TextColor));
}
BP_ApplyUIStyle(ActiveStyle.Button, VariantStyle);
} }
void UUIButton_Base::NativePreConstruct() void UUIButton_Base::NativePreConstruct()
{ {
Super::NativePreConstruct(); Super::NativePreConstruct();
RefreshUIStyle(); // preview no designer (usa defaults se não há GameInstance) InitializeButton();
ApplyInputActionStyle();
} }
void UUIButton_Base::NativeConstruct() void UUIButton_Base::NativeConstruct()
@@ -63,11 +199,23 @@ void UUIButton_Base::NativeConstruct()
} }
} }
RefreshUIStyle(); InitializeButton();
ApplyInputActionStyle();
if (bDeactivatedOnConstructWithTimeout && Timeout > 0.f)
{
StartDeactivateTimeout();
}
} }
void UUIButton_Base::NativeDestruct() void UUIButton_Base::NativeDestruct()
{ {
if (UWorld* World = GetWorld())
{
World->GetTimerManager().ClearTimer(InactivityTimer);
World->GetTimerManager().ClearTimer(TimerTickHandle);
}
if (bThemeBound) if (bThemeBound)
{ {
if (const UGameInstance* GI = GetGameInstance()) if (const UGameInstance* GI = GetGameInstance())
@@ -83,6 +231,46 @@ void UUIButton_Base::NativeDestruct()
Super::NativeDestruct(); Super::NativeDestruct();
} }
// ---- Hyper "Deactivate Button On Timeout" ----
void UUIButton_Base::StartDeactivateTimeout()
{
UWorld* World = GetWorld();
if (!World)
{
return;
}
SetIsEnabled(false);
TimeRemaining = Timeout;
World->GetTimerManager().SetTimer(
InactivityTimer, this, &UUIButton_Base::HandleTimeoutFinished, Timeout, false);
if (TimeUpdateFrequency > 0.f)
{
World->GetTimerManager().SetTimer(
TimerTickHandle, this, &UUIButton_Base::HandleTimerTick,
TimeUpdateFrequency, true);
}
}
void UUIButton_Base::HandleTimeoutFinished()
{
if (UWorld* World = GetWorld())
{
World->GetTimerManager().ClearTimer(TimerTickHandle);
}
TimeRemaining = 0.f;
SetIsEnabled(true);
OnButtonActivatedByTimeout.Broadcast();
}
void UUIButton_Base::HandleTimerTick()
{
TimeRemaining = FMath::Max(0.f, TimeRemaining - TimeUpdateFrequency);
OnButtonTimerUpdated.Broadcast(TimeRemaining);
}
void UUIButton_Base::HandleThemeChanged(FName /*NewThemeId*/) void UUIButton_Base::HandleThemeChanged(FName /*NewThemeId*/)
{ {
RefreshUIStyle(); RefreshUIStyle();

View File

@@ -2,23 +2,34 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "CommonButtonBase.h" #include "CommonButtonBase.h"
#include "Layout/Margin.h"
#include "Types/SlateEnums.h"
#include "UI/UIStyleTypes.h" #include "UI/UIStyleTypes.h"
#include "UI/UIStyleTokens.h" #include "UI/UIStyleTokens.h"
#include "UIButton_Base.generated.h" #include "UIButton_Base.generated.h"
class UBorder;
class UTextBlock;
class UImage;
class USizeBox;
class UCommonTextBlock;
class UCommonActionWidget;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUIButtonActivatedByTimeout);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUIButtonTimerUpdated, float, TimeRemaining);
/** /**
* Botão base do ZMMO. Camada de fundação (não instanciável direto — padrão * Botão base do ZMMO — porte 1:1 do UI_Button_Master do projeto Hyper para C++
* "_Abstract" do Hyper, mas em C++). * auto-configurável, trocando o Struct_Style_Button do Hyper pela camada de
* tema C++ (FUIStyle via UZMMOThemeSubsystem::GetActiveUIStyle()).
* *
* Divisão de responsabilidade (ARQUITETURA.md §3.3, exceção de UI): * Fundação: CommonUI (UCommonButtonBase) cuida de input/foco/click.
* - CommonUI (UCommonButtonBase): input teclado/gamepad, foco, click. * Camada Abstract (padrão "_Abstract" do Hyper). WBP UI_Button_Base herda
* - Este C++: resolve QUAIS tokens usar (variante + tema via * disto; UI_Button_Master herda do WBP. Os widgets visuais são opcionais
* UZMMOThemeSubsystem::GetActiveUIStyle()) e reage à troca de tema. * (BindWidgetOptional) e nomeados como no Hyper.
* - O WBP que herda (UI_Button_Master): decide COMO pintar, recebendo os
* tokens resolvidos em BP_ApplyUIStyle.
* *
* Hierarquia esperada: UUIButton_Base (C++ Abstract) * Auto-config: NativePreConstruct chama InitializeButton (text/number/icon/
* → UI_Button_Base (WBP abstrato) → UI_Button_Master → UI_Button_<Feature>. * sizebox/estilo) + ApplyInputActionStyle. Reage à troca de tema.
*/ */
UCLASS(Abstract, Blueprintable) UCLASS(Abstract, Blueprintable)
class ZMMO_API UUIButton_Base : public UCommonButtonBase class ZMMO_API UUIButton_Base : public UCommonButtonBase
@@ -26,11 +37,101 @@ class ZMMO_API UUIButton_Base : public UCommonButtonBase
GENERATED_BODY() GENERATED_BODY()
public: public:
/** Variante visual; o designer escolhe no WBP/instância. */ // ---- Botão ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Style") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button")
EUIButtonShape ButtonTypeSelection = EUIButtonShape::Rectangle;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button")
EUIButtonVariant Variant = EUIButtonVariant::Primary; EUIButtonVariant Variant = EUIButtonVariant::Primary;
/** Re-resolve os tokens do tema ativo e reaplica (chama BP_ApplyUIStyle). */ /** Hyper "Transform Policy = To Upper": força o texto em maiúsculas. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button")
bool bUppercaseText = true;
// ---- Text Style ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Text Style")
FText ButtonTextValue;
// ---- Number Text ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Number Text")
bool bShowNumberText = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Number Text")
int32 NumberTextAmount = 0;
// ---- Icon ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Icon")
bool bUseIconInsteadOfText = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Icon")
TObjectPtr<UObject> IconImage = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Icon")
EUIIconType IconTypeSelection = EUIIconType::None;
// ---- Size ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Size")
float InHeightOverride = 50.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Size")
float InWidthOverride = 150.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Size")
bool bDoNotOverrideWithSizebox = false;
// ---- Input Action Style ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style")
bool bShowInputActionWidget = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style")
FMargin InputPadding = FMargin(0.f, 0.f, 0.f, -22.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style")
TEnumAsByte<EHorizontalAlignment> InputHorzAlignment = HAlign_Center;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style")
TEnumAsByte<EVerticalAlignment> InputVertAlignment = VAlign_Bottom;
// ---- Deactivate by Timer ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deactivate By Timer")
bool bDeactivatedOnConstructWithTimeout = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deactivate By Timer")
float Timeout = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Timer")
float TimeUpdateFrequency = 0.05f;
UPROPERTY(BlueprintReadOnly, Category = "Timer")
float TimeRemaining = 0.f;
UPROPERTY(BlueprintAssignable, Category = "Timer")
FOnUIButtonActivatedByTimeout OnButtonActivatedByTimeout;
UPROPERTY(BlueprintAssignable, Category = "Timer")
FOnUIButtonTimerUpdated OnButtonTimerUpdated;
// ---- API (porte 1:1 das funções do Hyper) ----
UFUNCTION(BlueprintCallable, Category = "UI Button")
void SetButtonText(FText InText);
UFUNCTION(BlueprintCallable, Category = "UI Button")
void SetNumberText();
UFUNCTION(BlueprintCallable, Category = "UI Button")
void SetIcon();
UFUNCTION(BlueprintCallable, Category = "UI Button")
void ApplySizeBox();
UFUNCTION(BlueprintCallable, Category = "UI Button")
void ApplyInputActionStyle();
/** Orquestrador (Hyper "Initialize Button"): aplica tudo + estilo do tema. */
UFUNCTION(BlueprintCallable, Category = "UI Button")
void InitializeButton();
/** Re-resolve os tokens do tema ativo e reaplica (Hyper "Update Button Style"). */
UFUNCTION(BlueprintCallable, Category = "UI Style") UFUNCTION(BlueprintCallable, Category = "UI Style")
void RefreshUIStyle(); void RefreshUIStyle();
@@ -39,21 +140,39 @@ protected:
virtual void NativeConstruct() override; virtual void NativeConstruct() override;
virtual void NativeDestruct() override; virtual void NativeDestruct() override;
/** /** Hook opcional: o WBP pode estender/sobrescrever a aplicação visual. */
* Implementado no WBP: aplica os tokens resolvidos nos widgets visuais
* (Border/Text/etc.) que só o Blueprint conhece.
*/
UFUNCTION(BlueprintImplementableEvent, Category = "UI Style", UFUNCTION(BlueprintImplementableEvent, Category = "UI Style",
meta = (DisplayName = "Apply UI Style")) meta = (DisplayName = "Apply UI Style"))
void BP_ApplyUIStyle(const FUIStyleButton& Button, const FUIStyleButtonVariant& VariantStyle); void BP_ApplyUIStyle(const FUIStyleButton& Button, const FUIStyleButtonVariant& VariantStyle);
/** Seleciona o sub-struct da variante atual dentro de FUIStyleButton. */
const FUIStyleButtonVariant& ResolveVariant(const FUIStyleButton& Button) const; const FUIStyleButtonVariant& ResolveVariant(const FUIStyleButton& Button) const;
// ---- Widgets visuais opcionais (nomes espelham o Hyper) ----
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<USizeBox> SizeBox_Master;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> ButtonText;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UTextBlock> Number_Text;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UImage> Image_Icon;
/** Fundo temável (nossa adição sobre o Hyper, p/ FUIStyle). */
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UBorder> Bg;
private: private:
void StartDeactivateTimeout();
void HandleTimeoutFinished();
void HandleTimerTick();
UFUNCTION() UFUNCTION()
void HandleThemeChanged(FName NewThemeId); void HandleThemeChanged(FName NewThemeId);
/** Evita rebind duplicado do delegate OnThemeChanged. */
bool bThemeBound = false; bool bThemeBound = false;
FTimerHandle InactivityTimer;
FTimerHandle TimerTickHandle;
}; };