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:
@@ -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<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()
|
||||
{
|
||||
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();
|
||||
|
||||
@@ -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_<Feature>.
|
||||
* 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<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")
|
||||
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<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:
|
||||
void StartDeactivateTimeout();
|
||||
void HandleTimeoutFinished();
|
||||
void HandleTimerTick();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleThemeChanged(FName NewThemeId);
|
||||
|
||||
/** Evita rebind duplicado do delegate OnThemeChanged. */
|
||||
bool bThemeBound = false;
|
||||
FTimerHandle InactivityTimer;
|
||||
FTimerHandle TimerTickHandle;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user