wip: UI Button Base + content/config in-progress
Snapshot do trabalho em andamento na branch feat/ui-system: refinamentos do UIButton_Base (header+impl), atualizacao de styles/themes do botao, mapa de teste L_ZeusIATest, e demais .uasset/Config tocados durante a sessao de playtest do meshing. Commit conjunto a pedido do usuario pra deixar a branch limpa antes do proximo batch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,456 @@
|
||||
#include "UIButton_Base.h"
|
||||
|
||||
#include "CommonTextBlock.h"
|
||||
#include "Components/Image.h"
|
||||
#include "Components/OverlaySlot.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "Styling/SlateBrush.h"
|
||||
#include "Styling/SlateTypes.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "UI/UIStyleRow.h"
|
||||
#include "UI/UIStyleTokens.h"
|
||||
#include "Engine/DataTable.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr float MinTransition = 0.001f;
|
||||
|
||||
// Params do material UI_M_Button (Bg).
|
||||
static const FName P_TopColour("TopColour");
|
||||
static const FName P_BottomColour("BottomColour");
|
||||
static const FName P_GlowColour("GlowColour");
|
||||
static const FName P_GlowIntensity("GlowIntensity");
|
||||
static const FName P_CornerColour("CornerColour");
|
||||
static const FName P_CornerLength("CornerLength");
|
||||
static const FName P_CornerThickness("CornerThickness");
|
||||
static const FName P_FrameColour("FrameColour");
|
||||
static const FName P_AspectRatio("AspectRatio");
|
||||
static const FName P_GlowEdge("GlowEdge");
|
||||
static const FName P_GlowSoftness("GlowSoftness");
|
||||
|
||||
FORCEINLINE float EaseOutCubic(float Alpha)
|
||||
{
|
||||
return 1.f - FMath::Pow(1.f - Alpha, 3.f);
|
||||
}
|
||||
}
|
||||
|
||||
UUIButton_Base::UUIButton_Base(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
// Materiais default (fallback quando o brush da imagem não traz material).
|
||||
static ConstructorHelpers::FObjectFinder<UMaterialInterface> BgMatFinder(
|
||||
TEXT("/Game/ZMMO/UI/Themes/Default/Button/UI_M_Button.UI_M_Button"));
|
||||
if (BgMatFinder.Succeeded()) { BgMaterial = BgMatFinder.Object; }
|
||||
|
||||
static ConstructorHelpers::FObjectFinder<UMaterialInterface> GlowMatFinder(
|
||||
TEXT("/Game/ZMMO/UI/Themes/Default/Button/UI_M_Button_Glow.UI_M_Button_Glow"));
|
||||
if (GlowMatFinder.Succeeded()) { GlowMaterial = GlowMatFinder.Object; }
|
||||
|
||||
static ConstructorHelpers::FObjectFinder<UMaterialInterface> CornerMatFinder(
|
||||
TEXT("/Game/ZMMO/UI/Themes/Default/Button/UI_M_CornerBrackets.UI_M_CornerBrackets"));
|
||||
if (CornerMatFinder.Succeeded()) { CornerMaterial = CornerMatFinder.Object; }
|
||||
|
||||
static ConstructorHelpers::FObjectFinder<UMaterialInterface> FrameMatFinder(
|
||||
TEXT("/Game/ZMMO/UI/Themes/Default/Button/UI_M_Button_Frame.UI_M_Button_Frame"));
|
||||
if (FrameMatFinder.Succeeded()) { FrameMaterial = FrameMatFinder.Object; }
|
||||
|
||||
// Defaults = btn-primary (buttons_showcase.html). Cores em valores sRGB
|
||||
// diretos (mesma convenção do UUIProgressBar_Base — UI sem gamma).
|
||||
|
||||
// Default (repouso)
|
||||
DefaultVisual.TopColour = FLinearColor(0.910f, 0.753f, 0.376f, 0.18f);
|
||||
DefaultVisual.BottomColour = FLinearColor(0.659f, 0.471f, 0.188f, 0.10f);
|
||||
DefaultVisual.BorderColour = FLinearColor(0.831f, 0.659f, 0.314f, 0.28f);
|
||||
DefaultVisual.BorderWidth = 1.f;
|
||||
DefaultVisual.GlowColour = FLinearColor(0.f, 0.f, 0.f, 0.5f); // drop shadow (escura)
|
||||
DefaultVisual.GlowIntensity = 0.7f;
|
||||
DefaultVisual.TextColour = FLinearColor(1.f, 0.847f, 0.533f, 1.f);
|
||||
DefaultVisual.Scale = 1.f;
|
||||
DefaultVisual.RenderOpacity = 1.f;
|
||||
|
||||
// Hover — gradient mais forte, borda gold, glow aceso, leve zoom.
|
||||
HoveredVisual.TopColour = FLinearColor(0.910f, 0.753f, 0.376f, 0.32f);
|
||||
HoveredVisual.BottomColour = FLinearColor(0.659f, 0.471f, 0.188f, 0.18f);
|
||||
HoveredVisual.BorderColour = FLinearColor(0.910f, 0.753f, 0.439f, 1.f);
|
||||
HoveredVisual.BorderWidth = 1.f;
|
||||
HoveredVisual.GlowColour = FLinearColor(0.f, 0.f, 0.f, 0.55f); // sombra mais forte (elevado)
|
||||
HoveredVisual.GlowIntensity = 0.9f;
|
||||
HoveredVisual.TextColour = FLinearColor(1.f, 0.847f, 0.533f, 1.f);
|
||||
HoveredVisual.Scale = 1.02f;
|
||||
HoveredVisual.RenderOpacity = 1.f;
|
||||
|
||||
// Pressed/Active — gradient invertido, encolhe.
|
||||
PressedVisual.TopColour = FLinearColor(0.659f, 0.471f, 0.188f, 0.25f);
|
||||
PressedVisual.BottomColour = FLinearColor(0.910f, 0.753f, 0.376f, 0.15f);
|
||||
PressedVisual.BorderColour = FLinearColor(0.910f, 0.753f, 0.439f, 1.f);
|
||||
PressedVisual.BorderWidth = 1.f;
|
||||
PressedVisual.GlowColour = FLinearColor(0.f, 0.f, 0.f, 0.5f); // sombra menor (afundou)
|
||||
PressedVisual.GlowIntensity = 0.5f;
|
||||
PressedVisual.TextColour = FLinearColor(1.f, 0.847f, 0.533f, 1.f);
|
||||
PressedVisual.Scale = 0.98f;
|
||||
PressedVisual.RenderOpacity = 1.f;
|
||||
|
||||
// Disabled — quase apagado, texto escuro, opacity 0.6.
|
||||
DisabledVisual.TopColour = FLinearColor(1.f, 1.f, 1.f, 0.02f);
|
||||
DisabledVisual.BottomColour = FLinearColor(1.f, 1.f, 1.f, 0.02f);
|
||||
DisabledVisual.BorderColour = FLinearColor(1.f, 1.f, 1.f, 0.06f);
|
||||
DisabledVisual.BorderWidth = 1.f;
|
||||
DisabledVisual.GlowColour = FLinearColor(0.f, 0.f, 0.f, 0.25f); // sombra fraca
|
||||
DisabledVisual.GlowIntensity = 0.3f;
|
||||
DisabledVisual.TextColour = FLinearColor(0.227f, 0.220f, 0.188f, 1.f);
|
||||
DisabledVisual.Scale = 1.f;
|
||||
DisabledVisual.RenderOpacity = 0.6f;
|
||||
|
||||
// Selected (tabs/toggles) — gold suave persistente, sem zoom.
|
||||
SelectedVisual.TopColour = FLinearColor(0.910f, 0.753f, 0.376f, 0.20f);
|
||||
SelectedVisual.BottomColour = FLinearColor(0.659f, 0.471f, 0.188f, 0.12f);
|
||||
SelectedVisual.BorderColour = FLinearColor(0.831f, 0.659f, 0.314f, 0.55f);
|
||||
SelectedVisual.BorderWidth = 1.f;
|
||||
SelectedVisual.GlowColour = FLinearColor(0.f, 0.f, 0.f, 0.5f);
|
||||
SelectedVisual.GlowIntensity = 0.75f;
|
||||
SelectedVisual.TextColour = FLinearColor(1.f, 0.847f, 0.533f, 1.f);
|
||||
SelectedVisual.Scale = 1.f;
|
||||
SelectedVisual.RenderOpacity = 1.f;
|
||||
|
||||
CurrentVisual = DefaultVisual;
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativePreConstruct()
|
||||
{
|
||||
Super::NativePreConstruct();
|
||||
|
||||
EnsureMID();
|
||||
|
||||
// Padding/centralização do texto via slot do Overlay (design-time + runtime).
|
||||
if (ButtonText)
|
||||
{
|
||||
if (UOverlaySlot* OS = Cast<UOverlaySlot>(ButtonText->Slot))
|
||||
{
|
||||
OS->SetPadding(ContentPadding);
|
||||
OS->SetHorizontalAlignment(HAlign_Center);
|
||||
OS->SetVerticalAlignment(VAlign_Center);
|
||||
}
|
||||
}
|
||||
|
||||
// GlowFx (drop shadow): vaza pra fora via padding negativo + deslocado pra
|
||||
// baixo por GlowOffsetY (a sombra aparece mais embaixo, estilo box-shadow).
|
||||
if (GlowFx)
|
||||
{
|
||||
if (UOverlaySlot* OS = Cast<UOverlaySlot>(GlowFx->Slot))
|
||||
{
|
||||
OS->SetPadding(FMargin(-GlowExtent, -GlowExtent + GlowOffsetY,
|
||||
-GlowExtent, -GlowExtent - GlowOffsetY));
|
||||
OS->SetHorizontalAlignment(HAlign_Fill);
|
||||
OS->SetVerticalAlignment(VAlign_Fill);
|
||||
}
|
||||
}
|
||||
|
||||
// Corners puxado pra dentro (CornerInset>0) ou pra fora/abraçando (<0).
|
||||
if (Corners)
|
||||
{
|
||||
if (UOverlaySlot* OS = Cast<UOverlaySlot>(Corners->Slot))
|
||||
{
|
||||
OS->SetPadding(FMargin(CornerInset));
|
||||
OS->SetHorizontalAlignment(HAlign_Fill);
|
||||
OS->SetVerticalAlignment(VAlign_Fill);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentVisual = DefaultVisual;
|
||||
bTransitioning = false;
|
||||
ApplyVisual(CurrentVisual);
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
EnsureMID();
|
||||
bHoveredState = bPressedState = bSelectedState = false;
|
||||
bDisabledState = !GetIsEnabled();
|
||||
|
||||
// Estado inicial sem animar.
|
||||
const FUIButtonStateVisual& Target =
|
||||
bDisabledState ? DisabledVisual
|
||||
: bSelectedState ? SelectedVisual
|
||||
: DefaultVisual;
|
||||
CurrentVisual = Target;
|
||||
bTransitioning = false;
|
||||
ApplyVisual(CurrentVisual);
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
|
||||
{
|
||||
Super::NativeTick(MyGeometry, InDeltaTime);
|
||||
|
||||
if (!bTransitioning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionElapsed += InDeltaTime;
|
||||
const float Duration = FMath::Max(TransitionSeconds, MinTransition);
|
||||
const float Alpha = FMath::Clamp(TransitionElapsed / Duration, 0.f, 1.f);
|
||||
CurrentVisual = LerpVisual(FromVisual, ToVisual, EaseOutCubic(Alpha));
|
||||
ApplyVisual(CurrentVisual);
|
||||
|
||||
if (Alpha >= 1.f)
|
||||
{
|
||||
CurrentVisual = ToVisual;
|
||||
ApplyVisual(CurrentVisual);
|
||||
bTransitioning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// === Hooks de estado ===
|
||||
|
||||
void UUIButton_Base::NativeOnHovered()
|
||||
{
|
||||
Super::NativeOnHovered();
|
||||
bHoveredState = true;
|
||||
RefreshState();
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeOnUnhovered()
|
||||
{
|
||||
Super::NativeOnUnhovered();
|
||||
bHoveredState = false;
|
||||
RefreshState();
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeOnPressed()
|
||||
{
|
||||
Super::NativeOnPressed();
|
||||
bPressedState = true;
|
||||
RefreshState();
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeOnReleased()
|
||||
{
|
||||
Super::NativeOnReleased();
|
||||
bPressedState = false;
|
||||
RefreshState();
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeOnEnabled()
|
||||
{
|
||||
Super::NativeOnEnabled();
|
||||
bDisabledState = false;
|
||||
RefreshState();
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeOnDisabled()
|
||||
{
|
||||
Super::NativeOnDisabled();
|
||||
bDisabledState = true;
|
||||
RefreshState();
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeOnSelected(bool bBroadcast)
|
||||
{
|
||||
Super::NativeOnSelected(bBroadcast);
|
||||
bSelectedState = true;
|
||||
RefreshState();
|
||||
}
|
||||
|
||||
void UUIButton_Base::NativeOnDeselected(bool bBroadcast)
|
||||
{
|
||||
Super::NativeOnDeselected(bBroadcast);
|
||||
bSelectedState = false;
|
||||
RefreshState();
|
||||
}
|
||||
|
||||
// === Internos ===
|
||||
|
||||
void UUIButton_Base::RefreshState()
|
||||
{
|
||||
const FUIButtonStateVisual& Target =
|
||||
bDisabledState ? DisabledVisual
|
||||
: bPressedState ? PressedVisual
|
||||
: bHoveredState ? HoveredVisual
|
||||
: bSelectedState ? SelectedVisual
|
||||
: DefaultVisual;
|
||||
GoToState(Target);
|
||||
}
|
||||
|
||||
void UUIButton_Base::GoToState(const FUIButtonStateVisual& Target)
|
||||
{
|
||||
if (TransitionSeconds <= MinTransition)
|
||||
{
|
||||
CurrentVisual = Target;
|
||||
bTransitioning = false;
|
||||
ApplyVisual(CurrentVisual);
|
||||
return;
|
||||
}
|
||||
FromVisual = CurrentVisual;
|
||||
ToVisual = Target;
|
||||
TransitionElapsed = 0.f;
|
||||
bTransitioning = true;
|
||||
}
|
||||
|
||||
void UUIButton_Base::ApplyVisual(const FUIButtonStateVisual& V)
|
||||
{
|
||||
// Fundo: só gradient (o glow agora é a camada externa GlowFx).
|
||||
if (BgMID)
|
||||
{
|
||||
BgMID->SetVectorParameterValue(P_TopColour, V.TopColour);
|
||||
BgMID->SetVectorParameterValue(P_BottomColour, V.BottomColour);
|
||||
}
|
||||
// Halo externo (sai pra fora).
|
||||
if (GlowMID)
|
||||
{
|
||||
GlowMID->SetVectorParameterValue(P_GlowColour, V.GlowColour);
|
||||
GlowMID->SetScalarParameterValue(P_GlowIntensity, V.GlowIntensity);
|
||||
}
|
||||
// Cantos: cor acompanha a borda do estado (acende no hover); tamanho/peso
|
||||
// vêm das UPROPERTYs (estáticos por instância).
|
||||
if (CornerMID)
|
||||
{
|
||||
CornerMID->SetVectorParameterValue(P_CornerColour, V.BorderColour);
|
||||
CornerMID->SetScalarParameterValue(P_CornerLength, CornerLength);
|
||||
CornerMID->SetScalarParameterValue(P_CornerThickness, CornerThickness);
|
||||
}
|
||||
// Frame (material): mesma cor de borda do estado.
|
||||
if (BorderMID)
|
||||
{
|
||||
BorderMID->SetVectorParameterValue(P_FrameColour, V.BorderColour);
|
||||
}
|
||||
|
||||
const bool bSimple = (BorderStyle == EUIButtonBorderStyle::Simple);
|
||||
const bool bFrame = (BorderStyle == EUIButtonBorderStyle::Frame);
|
||||
const bool bCorners = bShowCorners; // INDEPENDENTE da borda
|
||||
|
||||
// Bg: canto arredondado sempre; a linha (outline) só no modo Simple.
|
||||
if (Bg)
|
||||
{
|
||||
FSlateBrush B = Bg->GetBrush();
|
||||
B.DrawAs = ESlateBrushDrawType::RoundedBox;
|
||||
FSlateBrushOutlineSettings Outline = B.OutlineSettings;
|
||||
Outline.CornerRadii = FVector4(CornerRadius, CornerRadius, CornerRadius, CornerRadius);
|
||||
Outline.RoundingType = ESlateBrushRoundingType::FixedRadius;
|
||||
if (bSimple)
|
||||
{
|
||||
Outline.Color = FSlateColor(V.BorderColour);
|
||||
Outline.Width = FMath::Max(V.BorderWidth, 0.f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Outline.Color = FSlateColor(FLinearColor::Transparent);
|
||||
Outline.Width = 0.f;
|
||||
}
|
||||
B.OutlineSettings = Outline;
|
||||
Bg->SetBrush(B);
|
||||
}
|
||||
|
||||
// Frame (moldura material) visível só no modo Frame.
|
||||
if (BorderFx)
|
||||
{
|
||||
BorderFx->SetVisibility(bFrame ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
|
||||
}
|
||||
// Cantos visíveis só no modo Corners.
|
||||
if (Corners)
|
||||
{
|
||||
Corners->SetVisibility(bCorners ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
|
||||
}
|
||||
|
||||
if (ButtonText)
|
||||
{
|
||||
ButtonText->SetColorAndOpacity(FSlateColor(V.TextColour));
|
||||
}
|
||||
|
||||
SetRenderScale(FVector2D(V.Scale, V.Scale));
|
||||
SetRenderOpacity(V.RenderOpacity);
|
||||
}
|
||||
|
||||
void UUIButton_Base::EnsureMID()
|
||||
{
|
||||
// Prioriza o material do próprio brush (se o WBP definiu um); senão usa o
|
||||
// material default da classe. Idempotente por imagem.
|
||||
auto Ensure = [this](UImage* Img, UMaterialInterface* Fallback) -> UMaterialInstanceDynamic*
|
||||
{
|
||||
if (!Img) { return nullptr; }
|
||||
UMaterialInterface* Mat = Cast<UMaterialInterface>(Img->GetBrush().GetResourceObject());
|
||||
if (!Mat) { Mat = Fallback; }
|
||||
if (!Mat) { return nullptr; }
|
||||
UMaterialInstanceDynamic* MID = UMaterialInstanceDynamic::Create(Mat, this);
|
||||
Img->SetBrushFromMaterial(MID);
|
||||
return MID;
|
||||
};
|
||||
if (!BgMID) { BgMID = Ensure(Bg, BgMaterial); }
|
||||
if (!GlowMID) { GlowMID = Ensure(GlowFx, GlowMaterial); }
|
||||
if (!CornerMID) { CornerMID = Ensure(Corners, CornerMaterial); }
|
||||
if (!BorderMID) { BorderMID = Ensure(BorderFx, FrameMaterial); }
|
||||
}
|
||||
|
||||
void UUIButton_Base::ApplyAspectToLayers() const
|
||||
{
|
||||
// Cada camada usa o aspect da SUA geometry (Corners/BorderFx têm padding
|
||||
// próprio, então tamanho ≠ do botão). Sem isso, os traços esticam.
|
||||
auto SetAspect = [](UMaterialInstanceDynamic* MID, const UImage* Img)
|
||||
{
|
||||
if (!MID || !Img) { return; }
|
||||
const FVector2D S = Img->GetCachedGeometry().GetLocalSize();
|
||||
if (S.Y > 1.f)
|
||||
{
|
||||
MID->SetScalarParameterValue(P_AspectRatio, static_cast<float>(S.X / S.Y));
|
||||
}
|
||||
};
|
||||
SetAspect(CornerMID, Corners);
|
||||
SetAspect(BorderMID, BorderFx);
|
||||
SetAspect(GlowMID, GlowFx);
|
||||
// Glow (drop shadow): ancora o anel da sombra na borda do BOTÃO — fração =
|
||||
// margem (GlowExtent) / meia-altura do GlowFx. Assim a sombra esmaece pros
|
||||
// dois lados a partir da borda, sem aresta dura na borda externa do quad.
|
||||
if (GlowMID && GlowFx)
|
||||
{
|
||||
const FVector2D S = GlowFx->GetCachedGeometry().GetLocalSize();
|
||||
if (S.Y > 1.f)
|
||||
{
|
||||
// Edge = fração (em inY=min(V,1-V)) onde está a borda do botão = margem/altura.
|
||||
const float Edge = GlowExtent / static_cast<float>(S.Y);
|
||||
GlowMID->SetScalarParameterValue(P_GlowEdge, Edge);
|
||||
// Largura do esmaecimento ~ a margem (escala com o tamanho do botão).
|
||||
GlowMID->SetScalarParameterValue(P_GlowSoftness, Edge * 1.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 UUIButton_Base::NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry,
|
||||
const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements,
|
||||
int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
// NativePaint roda no Designer E em runtime (o Tick não roda no Designer) —
|
||||
// é aqui que garantimos o aspect correto dos materiais em ambos.
|
||||
ApplyAspectToLayers();
|
||||
return Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements,
|
||||
LayerId, InWidgetStyle, bParentEnabled);
|
||||
}
|
||||
|
||||
FUIButtonStateVisual UUIButton_Base::LerpVisual(const FUIButtonStateVisual& A, const FUIButtonStateVisual& B, float T)
|
||||
{
|
||||
FUIButtonStateVisual R;
|
||||
R.TopColour = FMath::Lerp(A.TopColour, B.TopColour, T);
|
||||
R.BottomColour = FMath::Lerp(A.BottomColour, B.BottomColour, T);
|
||||
R.GlowColour = FMath::Lerp(A.GlowColour, B.GlowColour, T);
|
||||
R.GlowIntensity = FMath::Lerp(A.GlowIntensity, B.GlowIntensity, T);
|
||||
R.BorderColour = FMath::Lerp(A.BorderColour, B.BorderColour, T);
|
||||
R.BorderWidth = FMath::Lerp(A.BorderWidth, B.BorderWidth, T);
|
||||
R.TextColour = FMath::Lerp(A.TextColour, B.TextColour, T);
|
||||
R.Scale = FMath::Lerp(A.Scale, B.Scale, T);
|
||||
R.RenderOpacity = FMath::Lerp(A.RenderOpacity, B.RenderOpacity, T);
|
||||
return R;
|
||||
}
|
||||
|
||||
void UUIButton_Base::SetButtonText(const FText& InText)
|
||||
{
|
||||
if (ButtonText)
|
||||
{
|
||||
ButtonText->SetText(InText);
|
||||
}
|
||||
}
|
||||
|
||||
// === Acesso ao estilo ativo (DT_UI_Styles) — fase final ===
|
||||
|
||||
const FUIStyle* UUIButton_Base::GetActiveUIStyle(FName ThemeId) const
|
||||
{
|
||||
|
||||
@@ -4,53 +4,278 @@
|
||||
#include "CommonButtonBase.h"
|
||||
#include "UIButton_Base.generated.h"
|
||||
|
||||
class USizeBox;
|
||||
class UImage;
|
||||
class UCanvasPanel;
|
||||
class UCommonTextBlock;
|
||||
class UTextBlock;
|
||||
class UMaterialInterface;
|
||||
class UMaterialInstanceDynamic;
|
||||
|
||||
struct FUIStyle;
|
||||
struct FUIStyleButton;
|
||||
|
||||
/** Tipo de borda do botão (fixo por instância; a COR acende por estado). */
|
||||
UENUM(BlueprintType)
|
||||
enum class EUIButtonBorderStyle : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "Sem borda"),
|
||||
Simple UMETA(DisplayName = "Linha simples (Slate)"),
|
||||
Frame UMETA(DisplayName = "Frame (material)")
|
||||
};
|
||||
|
||||
/**
|
||||
* Botão base do ZMMO (CommonUI). Hoje expõe BindWidgetOptional dos widgets
|
||||
* que o WBP UI_Button_Master_New consome, e helpers pra ler o estilo ativo
|
||||
* de DT_UI_Styles. A aplicação visual (pintar brushes/cores/fonte) entra
|
||||
* depois — esta camada só prepara o acesso.
|
||||
* Snapshot visual de UM estado do botão (Default / Hover / Pressed / Disabled).
|
||||
* As cores de gradient/glow vão pro material (UI_M_Button via MID); a borda usa
|
||||
* o RoundedBox nativo do Slate (OutlineSettings) e o scale/opacity são
|
||||
* RenderTransform do widget. Transições entre estados interpolam esta struct
|
||||
* inteira (ver UUIButton_Base::NativeTick).
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ZMMO_API FUIButtonStateVisual
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Topo do gradient vertical do fundo (param TopColour do material). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State")
|
||||
FLinearColor TopColour = FLinearColor(0.910f, 0.753f, 0.376f, 0.18f);
|
||||
|
||||
/** Base do gradient vertical (param BottomColour). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State")
|
||||
FLinearColor BottomColour = FLinearColor(0.659f, 0.471f, 0.188f, 0.10f);
|
||||
|
||||
/** Cor do halo externo (camada GlowFx, material UI_M_Button_Glow). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State")
|
||||
FLinearColor GlowColour = FLinearColor(0.910f, 0.753f, 0.376f, 1.f);
|
||||
|
||||
/** Intensidade do halo externo (GlowFx). 0 = sem glow; hover acende (sai pra fora). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State",
|
||||
meta = (ClampMin = "0", ClampMax = "1"))
|
||||
float GlowIntensity = 0.f;
|
||||
|
||||
/** Cor da borda/cantos (Slate outline no modo Simple, ou material no modo Corners). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State")
|
||||
FLinearColor BorderColour = FLinearColor(0.831f, 0.659f, 0.314f, 0.28f);
|
||||
|
||||
/** Espessura da borda em pixels (Slate OutlineSettings.Width). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State",
|
||||
meta = (ClampMin = "0", ClampMax = "6"))
|
||||
float BorderWidth = 1.f;
|
||||
|
||||
/** Cor do texto (ButtonText->SetColorAndOpacity). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State")
|
||||
FLinearColor TextColour = FLinearColor(1.f, 0.847f, 0.533f, 1.f);
|
||||
|
||||
/** Scale do botão (CSS transform: scale). 1 = normal, 1.02 = hover, 0.98 = pressed. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State",
|
||||
meta = (ClampMin = "0.5", ClampMax = "1.5"))
|
||||
float Scale = 1.f;
|
||||
|
||||
/** Opacidade geral do botão (CSS opacity — disabled usa 0.6). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button|State",
|
||||
meta = (ClampMin = "0", ClampMax = "1"))
|
||||
float RenderOpacity = 1.f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Botão base do ZMMO (CommonUI). Mesmo padrão do UUIProgressBar_Base: um UImage
|
||||
* (`Bg`) com material custom (UI_M_Button) controlado por MID + RoundedBox nativo
|
||||
* do Slate pra borda/canto. Os estados do UCommonButtonBase (Hover/Pressed/
|
||||
* Disabled/Selected) trocam o FUIButtonStateVisual alvo; a transição interpola
|
||||
* suave no Tick (estilo CSS `transition`).
|
||||
*
|
||||
* Modo atual = manual (os 4 FUIButtonStateVisual editáveis no Details, já com
|
||||
* defaults do `btn-primary`). A leitura via DT_UI_Styles (bUseTheme + Variant)
|
||||
* entra na fase final, reusando esta struct.
|
||||
*
|
||||
* WBP filho (UI_Button_Master_New): `Bg` (UImage, obrigatório, material no brush)
|
||||
* + `ButtonText` (UCommonTextBlock, opcional).
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class ZMMO_API UUIButton_Base : public UCommonButtonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UUIButton_Base(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/** Define o texto do botão (ButtonText). */
|
||||
UFUNCTION(BlueprintCallable, Category = "UI Button")
|
||||
void SetButtonText(const FText& InText);
|
||||
|
||||
// === Visual por estado (modo manual; defaults = btn-primary) ===
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual")
|
||||
FUIButtonStateVisual DefaultVisual;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual")
|
||||
FUIButtonStateVisual HoveredVisual;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual")
|
||||
FUIButtonStateVisual PressedVisual;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual")
|
||||
FUIButtonStateVisual DisabledVisual;
|
||||
|
||||
/** Estado "selecionado" (tabs/toggles). Default = igual ao Hovered. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual")
|
||||
FUIButtonStateVisual SelectedVisual;
|
||||
|
||||
/** Raio do canto em pixels (Slate RoundedBox). Comum a todos os estados. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual",
|
||||
meta = (ClampMin = "0", ClampMax = "32"))
|
||||
float CornerRadius = 2.f;
|
||||
|
||||
/** Tipo de borda (independente dos cantos): sem borda, linha Slate ou frame material. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual")
|
||||
EUIButtonBorderStyle BorderStyle = EUIButtonBorderStyle::Frame;
|
||||
|
||||
/** Liga os cantos (corner brackets) — INDEPENDENTE da borda. Pode ter os dois,
|
||||
* só um, ou nenhum. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual")
|
||||
bool bShowCorners = true;
|
||||
|
||||
/** Material do fundo (UI_M_Button). Fallback quando o brush do Bg não traz
|
||||
* material próprio. Defaults resolvidos no construtor. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Button|Visual")
|
||||
TObjectPtr<UMaterialInterface> BgMaterial;
|
||||
|
||||
/** Material do halo externo (UI_M_Button_Glow), aplicado no GlowFx. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Button|Visual")
|
||||
TObjectPtr<UMaterialInterface> GlowMaterial;
|
||||
|
||||
/** Material dos cantos (UI_M_CornerBrackets), aplicado no Corners. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Button|Visual")
|
||||
TObjectPtr<UMaterialInterface> CornerMaterial;
|
||||
|
||||
/** Material da moldura (UI_M_Button_Frame), aplicado no BorderFx. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Button|Visual")
|
||||
TObjectPtr<UMaterialInterface> FrameMaterial;
|
||||
|
||||
/** Padding do conteúdo (texto) dentro do botão. btn-primary = 28px horiz /
|
||||
* 12px vert. Aplicado no slot do ButtonText em PreConstruct (o texto fica
|
||||
* centralizado). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual")
|
||||
FMargin ContentPadding = FMargin(28.f, 12.f, 28.f, 12.f);
|
||||
|
||||
/** Quanto o halo externo (GlowFx) vaza pra fora do botão, em pixels. Aplicado
|
||||
* como padding negativo no slot do GlowFx em PreConstruct. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual",
|
||||
meta = (ClampMin = "0", ClampMax = "64"))
|
||||
float GlowExtent = 16.f;
|
||||
|
||||
/** Desloca os cantos. Negativo = pra FORA (abraçando o botão, estilo CSS);
|
||||
* positivo = pra dentro. Aplicado no slot do Corners em PreConstruct. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual",
|
||||
meta = (ClampMin = "-32", ClampMax = "32"))
|
||||
float CornerInset = -5.f;
|
||||
|
||||
/** Comprimento de cada braço do "L" (fração da altura). Maior = L mais longo. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual",
|
||||
meta = (ClampMin = "0.05", ClampMax = "1.0"))
|
||||
float CornerLength = 0.35f;
|
||||
|
||||
/** Espessura/peso do traço dos cantos (fração da altura). Menor = mais fino. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual",
|
||||
meta = (ClampMin = "0.005", ClampMax = "0.3"))
|
||||
float CornerThickness = 0.04f;
|
||||
|
||||
/** Deslocamento da sombra (GlowFx) pra baixo, em px — dá o look de drop shadow. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Visual",
|
||||
meta = (ClampMin = "0", ClampMax = "32"))
|
||||
float GlowOffsetY = 4.f;
|
||||
|
||||
/** Duração da transição entre estados (s). 0 = troca instantânea. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI Button|Animation",
|
||||
meta = (ClampMin = "0", ClampMax = "1"))
|
||||
float TransitionSeconds = 0.12f;
|
||||
|
||||
protected:
|
||||
// ---- Widgets BindWidgetOptional (espelham a árvore do UI_Button_Master_New) ----
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<USizeBox> SizeBox;
|
||||
virtual void NativePreConstruct() override;
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
|
||||
virtual int32 NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry,
|
||||
const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements,
|
||||
int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> ImgStoke;
|
||||
// === Hooks de estado do UCommonButtonBase ===
|
||||
virtual void NativeOnHovered() override;
|
||||
virtual void NativeOnUnhovered() override;
|
||||
virtual void NativeOnPressed() override;
|
||||
virtual void NativeOnReleased() override;
|
||||
virtual void NativeOnEnabled() override;
|
||||
virtual void NativeOnDisabled() override;
|
||||
virtual void NativeOnSelected(bool bBroadcast) override;
|
||||
virtual void NativeOnDeselected(bool bBroadcast) override;
|
||||
|
||||
// === BindWidget (espelham a árvore do UI_Button_Master_New) ===
|
||||
// Todos BindWidgetOptional pra não quebrar WBPs que herdam UUIButton_Base sem
|
||||
// essas peças — sem elas o visual degrada (não pinta), mas não quebra.
|
||||
|
||||
/** Halo externo (atrás do Bg, com padding negativo pra vazar pra fora). */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UCanvasPanel> CanvasPanel_Text;
|
||||
TObjectPtr<UImage> GlowFx;
|
||||
|
||||
/** Fundo (gradient). */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> Bg;
|
||||
|
||||
/** Moldura/frame (material), visível só no BorderStyle = Frame. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> BorderFx;
|
||||
|
||||
/** Cantos (corner brackets), visível só no BorderStyle = Corners. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> Corners;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UCommonTextBlock> ButtonText;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UTextBlock> Number_Text;
|
||||
// === Acesso ao estilo ativo (DT_UI_Styles) — usado na fase final ===
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> ImgIcon;
|
||||
|
||||
// ---- Acesso ao estilo ativo (DT_UI_Styles) — C++ only ----
|
||||
|
||||
/** Carrega DT_UI_Styles e devolve o FUIStyle da row pedida (default "Default").
|
||||
* Retorna nullptr se a DT ou a row não existirem. Ponteiro válido enquanto
|
||||
* a DT estiver carregada — uso típico é leitura imediata, sem armazenar. */
|
||||
/** Carrega DT_UI_Styles e devolve o FUIStyle da row pedida (default "Default"). */
|
||||
const FUIStyle* GetActiveUIStyle(FName ThemeId = TEXT("Default")) const;
|
||||
|
||||
/** Atalho: devolve só o bloco Button do FUIStyle ativo. */
|
||||
const FUIStyleButton* GetActiveButtonStyle(FName ThemeId = TEXT("Default")) const;
|
||||
|
||||
private:
|
||||
/** Cria os MIDs a partir do material no brush de cada imagem (idempotente). */
|
||||
void EnsureMID();
|
||||
|
||||
/** Passa a proporção W/H de CADA camada (cantos/frame/glow) pro respectivo
|
||||
* material que compensa aspect. Chamado no NativePaint — roda no Designer E
|
||||
* em runtime (o Tick NÃO roda no Designer, por isso o traço esticava lá). */
|
||||
void ApplyAspectToLayers() const;
|
||||
|
||||
/** Recalcula o estado-alvo (disabled>pressed>hovered>selected>default) e transiciona. */
|
||||
void RefreshState();
|
||||
|
||||
/** Inicia transição pro visual alvo (ou aplica direto se TransitionSeconds≈0). */
|
||||
void GoToState(const FUIButtonStateVisual& Target);
|
||||
|
||||
/** Empurra um visual pros widgets (MID + borda Slate + scale/opacity + texto). */
|
||||
void ApplyVisual(const FUIButtonStateVisual& V);
|
||||
|
||||
static FUIButtonStateVisual LerpVisual(const FUIButtonStateVisual& A, const FUIButtonStateVisual& B, float T);
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> BgMID;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> GlowMID;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> CornerMID;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> BorderMID;
|
||||
|
||||
bool bHoveredState = false;
|
||||
bool bPressedState = false;
|
||||
bool bDisabledState = false;
|
||||
bool bSelectedState = false;
|
||||
|
||||
FUIButtonStateVisual FromVisual;
|
||||
FUIButtonStateVisual ToVisual;
|
||||
FUIButtonStateVisual CurrentVisual;
|
||||
float TransitionElapsed = 0.f;
|
||||
bool bTransitioning = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user