Co-authored-by: Mateus Rodrigues <mateuus27@outlook.com> Co-committed-by: Mateus Rodrigues <mateuus27@outlook.com>
435 lines
11 KiB
C++
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();
|
|
}
|