From 91b080aa68a407c450a8e8bfecefa9d74fce2ab2 Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Mon, 18 May 2026 21:58:10 -0300 Subject: [PATCH] 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) --- Source/ZMMO/Game/UI/Widgets/UIButton_Base.cpp | 194 +++++++++++++++++- Source/ZMMO/Game/UI/Widgets/UIButton_Base.h | 157 ++++++++++++-- 2 files changed, 329 insertions(+), 22 deletions(-) diff --git a/Source/ZMMO/Game/UI/Widgets/UIButton_Base.cpp b/Source/ZMMO/Game/UI/Widgets/UIButton_Base.cpp index 431e66b..672c80d 100644 --- a/Source/ZMMO/Game/UI/Widgets/UIButton_Base.cpp +++ b/Source/ZMMO/Game/UI/Widgets/UIButton_Base.cpp @@ -2,6 +2,15 @@ #include "ZMMOThemeSubsystem.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 { @@ -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( + 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(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() { const FUIStyle Fallback; // defaults do struct = paleta Aurora Arcana 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() { Super::NativePreConstruct(); - RefreshUIStyle(); // preview no designer (usa defaults se não há GameInstance) + InitializeButton(); + ApplyInputActionStyle(); } 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() { + if (UWorld* World = GetWorld()) + { + World->GetTimerManager().ClearTimer(InactivityTimer); + World->GetTimerManager().ClearTimer(TimerTickHandle); + } + if (bThemeBound) { if (const UGameInstance* GI = GetGameInstance()) @@ -83,6 +231,46 @@ void UUIButton_Base::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*/) { RefreshUIStyle(); diff --git a/Source/ZMMO/Game/UI/Widgets/UIButton_Base.h b/Source/ZMMO/Game/UI/Widgets/UIButton_Base.h index 6b1c934..5f24fa4 100644 --- a/Source/ZMMO/Game/UI/Widgets/UIButton_Base.h +++ b/Source/ZMMO/Game/UI/Widgets/UIButton_Base.h @@ -2,23 +2,34 @@ #include "CoreMinimal.h" #include "CommonButtonBase.h" +#include "Layout/Margin.h" +#include "Types/SlateEnums.h" #include "UI/UIStyleTypes.h" #include "UI/UIStyleTokens.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 - * "_Abstract" do Hyper, mas em C++). + * Botão base do ZMMO — porte 1:1 do UI_Button_Master do projeto Hyper para 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): - * - CommonUI (UCommonButtonBase): input teclado/gamepad, foco, click. - * - Este C++: resolve QUAIS tokens usar (variante + tema via - * UZMMOThemeSubsystem::GetActiveUIStyle()) e reage à troca de tema. - * - O WBP que herda (UI_Button_Master): decide COMO pintar, recebendo os - * tokens resolvidos em BP_ApplyUIStyle. + * Fundação: CommonUI (UCommonButtonBase) cuida de input/foco/click. + * Camada Abstract (padrão "_Abstract" do Hyper). WBP UI_Button_Base herda + * disto; UI_Button_Master herda do WBP. Os widgets visuais são opcionais + * (BindWidgetOptional) e nomeados como no Hyper. * - * Hierarquia esperada: UUIButton_Base (C++ Abstract) - * → UI_Button_Base (WBP abstrato) → UI_Button_Master → UI_Button_. + * Auto-config: NativePreConstruct chama InitializeButton (text/number/icon/ + * sizebox/estilo) + ApplyInputActionStyle. Reage à troca de tema. */ UCLASS(Abstract, Blueprintable) class ZMMO_API UUIButton_Base : public UCommonButtonBase @@ -26,11 +37,101 @@ class ZMMO_API UUIButton_Base : public UCommonButtonBase GENERATED_BODY() public: - /** Variante visual; o designer escolhe no WBP/instância. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Style") + // ---- Botão ---- + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button") + EUIButtonShape ButtonTypeSelection = EUIButtonShape::Rectangle; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button") 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 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 InputHorzAlignment = HAlign_Center; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style") + TEnumAsByte 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") void RefreshUIStyle(); @@ -39,21 +140,39 @@ protected: virtual void NativeConstruct() override; virtual void NativeDestruct() override; - /** - * Implementado no WBP: aplica os tokens resolvidos nos widgets visuais - * (Border/Text/etc.) que só o Blueprint conhece. - */ + /** Hook opcional: o WBP pode estender/sobrescrever a aplicação visual. */ UFUNCTION(BlueprintImplementableEvent, Category = "UI Style", meta = (DisplayName = "Apply UI Style")) 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; + // ---- Widgets visuais opcionais (nomes espelham o Hyper) ---- + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr SizeBox_Master; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr ButtonText; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Number_Text; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Image_Icon; + + /** Fundo temável (nossa adição sobre o Hyper, p/ FUIStyle). */ + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Bg; + private: + void StartDeactivateTimeout(); + void HandleTimeoutFinished(); + void HandleTimerTick(); + UFUNCTION() void HandleThemeChanged(FName NewThemeId); - /** Evita rebind duplicado do delegate OnThemeChanged. */ bool bThemeBound = false; + FTimerHandle InactivityTimer; + FTimerHandle TimerTickHandle; };