Files
ZMMO/Source/ZMMO/Game/UI/Widgets/UIButton_Base.cpp
2026-05-23 02:16:27 -03:00

435 lines
11 KiB
C++

#include "UIButton_Base.h"
#include "ZMMOThemeSubsystem.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "Components/Border.h"
#include "Styling/SlateBrush.h"
#include "Components/TextBlock.h"
#include "Components/Image.h"
#include "Components/SizeBox.h"
#include "Components/ScaleBox.h"
#include "Components/OverlaySlot.h"
#include "CommonTextBlock.h"
#include "CommonActionWidget.h"
namespace
{
/** Resolve o FUIStyle ativo; fallback p/ defaults (preview de designer). */
const FUIStyle& ResolveActiveStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
{
if (Widget)
{
if (const UGameInstance* GI = Widget->GetGameInstance())
{
if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
return Theme->GetActiveUIStyle();
}
}
}
return Fallback; // sem GameInstance (design-time): Aurora Arcana default
}
}
FUIStyleButtonVariant UUIButton_Base::ResolveVariant(const FUIStyleButton& Button) const
{
// Compõe FLAT a partir dos 3 sub-mapas do tema (Backgrounds/Strokes/Fonts).
// Fallback inline → Defaults (constructor de FUIStyleButton popula
// Primary/Secondary/Danger/Ghost com defaults Aurora Arcana — GC-safe).
static const FUIStyleButton Defaults;
FUIStyleButtonVariant V;
const FUIStyleButtonBackground* BG = Button.Backgrounds.Find(Variant);
if (!BG) { BG = Defaults.Backgrounds.Find(Variant); }
if (!BG) { BG = Defaults.Backgrounds.Find(TEXT("Primary")); }
if (BG)
{
V.BgNormal = BG->BgNormal;
V.BgHover = BG->BgHover;
V.BgPressed = BG->BgPressed;
V.BgDisabled = BG->BgDisabled;
V.BackgroundTexture = BG->BackgroundTexture;
V.BackgroundMargin = BG->BackgroundMargin;
}
const FUIStyleButtonStroke* ST = Button.Strokes.Find(Variant);
if (!ST) { ST = Defaults.Strokes.Find(Variant); }
if (!ST) { ST = Defaults.Strokes.Find(TEXT("Primary")); }
if (ST)
{
V.BorderNormal = ST->BorderNormal;
V.BorderHover = ST->BorderHover;
V.BorderWidth = ST->BorderWidth;
V.CornerRadius = ST->CornerRadius;
}
const FUIStyleButtonFont* F = Button.Fonts.Find(Variant);
if (!F) { F = Defaults.Fonts.Find(Variant); }
if (!F) { F = Defaults.Fonts.Find(TEXT("Primary")); }
if (F)
{
V.TextColor = F->TextColor;
V.TextStyle = F->TextStyle;
}
return V;
}
TArray<FString> UUIButton_Base::GetVariantOptions() const
{
TArray<FString> Options;
for (const TCHAR* N : { TEXT("Primary"), TEXT("Secondary"), TEXT("Danger"), TEXT("Ghost") })
{
Options.Add(N);
}
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
{
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
FName(TEXT("Default")), TEXT("UIButtonVariantOptions"), false))
{
for (const TPair<FName, FUIStyleButtonBackground>& P : Row->Style.Button.Backgrounds) { Options.AddUnique(P.Key.ToString()); }
for (const TPair<FName, FUIStyleButtonStroke>& P : Row->Style.Button.Strokes) { Options.AddUnique(P.Key.ToString()); }
for (const TPair<FName, FUIStyleButtonFont>& P : Row->Style.Button.Fonts) { Options.AddUnique(P.Key.ToString()); }
}
}
return Options;
}
// ---- 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);
const FUIStyleButtonVariant& VariantStyle = ResolveVariant(ActiveStyle.Button);
// Fundo + borda no estado Normal (hover/pressed/disabled via NativeOn*).
ApplyVisualState(EUIButtonVisual::Normal);
if (ButtonText)
{
// Tipografia: CommonTextStyle da variante (padrão Hyper/CommonUI).
if (VariantStyle.TextStyle)
{
ButtonText->SetStyle(VariantStyle.TextStyle);
}
// Cor: FUIStyle manda na cor por tema (sobrepõe o CommonTextStyle).
ButtonText->SetColorAndOpacity(FSlateColor(VariantStyle.TextColor));
// Alinhamento L/C/R dentro da largura do botão.
ButtonText->SetJustification(TextAlign);
}
// Texto adapta ao tamanho do botão (ScaleToFit) ou usa tamanho fixo.
if (TextScaleBox)
{
TextScaleBox->SetStretch(bAutoSizeText ? EStretch::ScaleToFit : EStretch::None);
// Posiciona o bloco de texto L/C/R dentro do botão (slot do Overlay).
if (UOverlaySlot* OS = Cast<UOverlaySlot>(TextScaleBox->Slot))
{
EHorizontalAlignment H = HAlign_Center;
switch (TextAlign)
{
case ETextJustify::Left: H = HAlign_Left; break;
case ETextJustify::Right: H = HAlign_Right; break;
default: H = HAlign_Center; break;
}
OS->SetHorizontalAlignment(H);
OS->SetVerticalAlignment(VAlign_Center);
}
}
BP_ApplyUIStyle(ActiveStyle.Button, VariantStyle);
}
void UUIButton_Base::ApplyVisualState(EUIButtonVisual State)
{
if (!Background)
{
return;
}
const FUIStyle Fallback;
const FUIStyle& AS = ResolveActiveStyle(this, Fallback);
const FUIStyleButtonVariant& V = ResolveVariant(AS.Button);
FLinearColor Fill, Outline;
switch (State)
{
case EUIButtonVisual::Hovered: Fill = V.BgHover; Outline = V.BorderHover; break;
case EUIButtonVisual::Pressed: Fill = V.BgPressed; Outline = V.BorderHover; break;
case EUIButtonVisual::Disabled: Fill = V.BgDisabled; Outline = V.BorderNormal; break;
default: Fill = V.BgNormal; Outline = V.BorderNormal; break;
}
FSlateBrush Brush = Background->Background;
Brush.TintColor = FSlateColor(Fill);
if (bRoundedBackground)
{
// CornerRadius/BorderWidth agora vêm da variante composta (Stroke).
const float R = V.CornerRadius;
Brush.DrawAs = ESlateBrushDrawType::RoundedBox;
Brush.OutlineSettings = FSlateBrushOutlineSettings(
FVector4(R, R, R, R), FSlateColor(Outline), V.BorderWidth);
Brush.OutlineSettings.RoundingType = ESlateBrushRoundingType::FixedRadius;
}
else
{
Brush.DrawAs = ESlateBrushDrawType::Box;
Brush.OutlineSettings = FSlateBrushOutlineSettings(
FVector4(0, 0, 0, 0), FSlateColor(Outline), V.BorderWidth);
}
Background->SetBrush(Brush);
}
void UUIButton_Base::NativeOnHovered()
{
Super::NativeOnHovered();
ApplyVisualState(EUIButtonVisual::Hovered);
}
void UUIButton_Base::NativeOnUnhovered()
{
Super::NativeOnUnhovered();
ApplyVisualState(EUIButtonVisual::Normal);
}
void UUIButton_Base::NativeOnPressed()
{
Super::NativeOnPressed();
ApplyVisualState(EUIButtonVisual::Pressed);
}
void UUIButton_Base::NativeOnReleased()
{
Super::NativeOnReleased();
ApplyVisualState(IsHovered() ? EUIButtonVisual::Hovered : EUIButtonVisual::Normal);
}
void UUIButton_Base::NativeOnEnabled()
{
Super::NativeOnEnabled();
ApplyVisualState(EUIButtonVisual::Normal);
}
void UUIButton_Base::NativeOnDisabled()
{
Super::NativeOnDisabled();
ApplyVisualState(EUIButtonVisual::Disabled);
}
void UUIButton_Base::NativePreConstruct()
{
Super::NativePreConstruct();
InitializeButton();
ApplyInputActionStyle();
}
void UUIButton_Base::NativeConstruct()
{
Super::NativeConstruct();
if (!bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.AddDynamic(this, &UUIButton_Base::HandleThemeChanged);
bThemeBound = true;
}
}
}
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())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.RemoveDynamic(this, &UUIButton_Base::HandleThemeChanged);
}
}
bThemeBound = false;
}
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();
}