diff --git a/Content/ZMMO/UI/Shared/UI_ProgressBar_Master.uasset b/Content/ZMMO/UI/Shared/UI_ProgressBar_Master.uasset new file mode 100644 index 0000000..40cc97a Binary files /dev/null and b/Content/ZMMO/UI/Shared/UI_ProgressBar_Master.uasset differ diff --git a/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar.uasset b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar.uasset new file mode 100644 index 0000000..a24dffe Binary files /dev/null and b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar.uasset differ diff --git a/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_HP.uasset b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_HP.uasset new file mode 100644 index 0000000..902fd3b Binary files /dev/null and b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_HP.uasset differ diff --git a/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp b/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp new file mode 100644 index 0000000..4120258 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp @@ -0,0 +1,278 @@ +#include "UIProgressBar_Base.h" + +#include "Components/Image.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Materials/MaterialInterface.h" +#include "Styling/SlateBrush.h" + +namespace +{ + constexpr float MinAnimSeconds = 0.001f; + + static const FName P_PrimaryLevel("PrimaryLevel"); + static const FName P_SecondaryLevel("SecondaryLevel"); + static const FName P_PrimaryColour("PrimaryColour"); + static const FName P_PrimaryColourBottom("PrimaryColourBottom"); + static const FName P_SecondaryColour("SecondaryColour"); + static const FName P_SecondaryColourBottom("SecondaryColourBottom"); + static const FName P_EmptyColour("EmptyColour"); + static const FName P_EnableGradient("EnableGradient"); + static const FName P_GradientSplit("GradientSplit"); + + FORCEINLINE float EaseOutCubic(float Alpha) + { + return 1.f - FMath::Pow(1.f - Alpha, 3.f); + } +} + +void UUIProgressBar_Base::NativePreConstruct() +{ + Super::NativePreConstruct(); + + CurrentPrimaryLevel = InitialPrimaryLevel; + TargetPrimaryLevel = InitialPrimaryLevel; + PrimaryStart = InitialPrimaryLevel; + PrimaryElapsed = 0.f; + bAnimatingPrimary = false; + + CurrentSecondaryLevel = InitialSecondaryLevel; + TargetSecondaryLevel = InitialSecondaryLevel; + SecondaryStart = InitialSecondaryLevel; + SecondaryElapsed = 0.f; + bAnimatingSecondary = false; + + EnsureMID(); + ApplyOverridesToMID(); + ApplyPrimaryToMID(CurrentPrimaryLevel); + ApplySecondaryToMID(CurrentSecondaryLevel); +} + +void UUIProgressBar_Base::NativeConstruct() +{ + Super::NativeConstruct(); + + EnsureMID(); + ApplyOverridesToMID(); + ApplyPrimaryToMID(CurrentPrimaryLevel); + ApplySecondaryToMID(CurrentSecondaryLevel); +} + +void UUIProgressBar_Base::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + if (!bAnimatingPrimary && !bAnimatingSecondary) + { + return; + } + + if (bAnimatingPrimary) + { + PrimaryElapsed += InDeltaTime; + const float Duration = FMath::Max(PrimaryAnimationSeconds, MinAnimSeconds); + const float Alpha = FMath::Clamp(PrimaryElapsed / Duration, 0.f, 1.f); + CurrentPrimaryLevel = FMath::Lerp(PrimaryStart, TargetPrimaryLevel, EaseOutCubic(Alpha)); + ApplyPrimaryToMID(CurrentPrimaryLevel); + if (Alpha >= 1.f) + { + CurrentPrimaryLevel = TargetPrimaryLevel; + ApplyPrimaryToMID(CurrentPrimaryLevel); + bAnimatingPrimary = false; + } + } + + if (bAnimatingSecondary) + { + SecondaryElapsed += InDeltaTime; + const float Duration = FMath::Max(SecondaryAnimationSeconds, MinAnimSeconds); + const float Alpha = FMath::Clamp(SecondaryElapsed / Duration, 0.f, 1.f); + CurrentSecondaryLevel = FMath::Lerp(SecondaryStart, TargetSecondaryLevel, EaseOutCubic(Alpha)); + ApplySecondaryToMID(CurrentSecondaryLevel); + if (Alpha >= 1.f) + { + CurrentSecondaryLevel = TargetSecondaryLevel; + ApplySecondaryToMID(CurrentSecondaryLevel); + bAnimatingSecondary = false; + } + } +} + +void UUIProgressBar_Base::SetTargetPrimaryLevel(float Primary01) +{ + const float Clamped = FMath::Clamp(Primary01, 0.f, 1.f); + if (PrimaryAnimationSeconds <= MinAnimSeconds) + { + SetPrimaryLevelImmediate(Clamped); + return; + } + if (FMath::IsNearlyEqual(Clamped, CurrentPrimaryLevel, KINDA_SMALL_NUMBER) && + FMath::IsNearlyEqual(Clamped, TargetPrimaryLevel, KINDA_SMALL_NUMBER)) + { + return; + } + TargetPrimaryLevel = Clamped; + PrimaryStart = CurrentPrimaryLevel; + PrimaryElapsed = 0.f; + bAnimatingPrimary = true; +} + +void UUIProgressBar_Base::SetPrimaryLevelImmediate(float Primary01) +{ + const float Clamped = FMath::Clamp(Primary01, 0.f, 1.f); + CurrentPrimaryLevel = Clamped; + TargetPrimaryLevel = Clamped; + PrimaryStart = Clamped; + PrimaryElapsed = 0.f; + bAnimatingPrimary = false; + ApplyPrimaryToMID(CurrentPrimaryLevel); +} + +void UUIProgressBar_Base::SetTargetSecondaryLevel(float Sec01) +{ + const float Clamped = FMath::Clamp(Sec01, 0.f, 1.f); + if (SecondaryAnimationSeconds <= MinAnimSeconds) + { + SetSecondaryLevelImmediate(Clamped); + return; + } + if (FMath::IsNearlyEqual(Clamped, CurrentSecondaryLevel, KINDA_SMALL_NUMBER) && + FMath::IsNearlyEqual(Clamped, TargetSecondaryLevel, KINDA_SMALL_NUMBER)) + { + return; + } + TargetSecondaryLevel = Clamped; + SecondaryStart = CurrentSecondaryLevel; + SecondaryElapsed = 0.f; + bAnimatingSecondary = true; +} + +void UUIProgressBar_Base::SetSecondaryLevelImmediate(float Sec01) +{ + const float Clamped = FMath::Clamp(Sec01, 0.f, 1.f); + CurrentSecondaryLevel = Clamped; + TargetSecondaryLevel = Clamped; + SecondaryStart = Clamped; + SecondaryElapsed = 0.f; + bAnimatingSecondary = false; + ApplySecondaryToMID(CurrentSecondaryLevel); +} + +void UUIProgressBar_Base::SetPrimaryColour(FLinearColor Color) +{ + PrimaryColour = Color; + bOverridePrimaryColour = true; + if (FillMID) FillMID->SetVectorParameterValue(P_PrimaryColour, Color); +} + +void UUIProgressBar_Base::SetPrimaryColourBottom(FLinearColor Color) +{ + PrimaryColourBottom = Color; + bOverridePrimaryColourBottom = true; + if (FillMID) FillMID->SetVectorParameterValue(P_PrimaryColourBottom, Color); +} + +void UUIProgressBar_Base::SetSecondaryColour(FLinearColor Color) +{ + SecondaryColour = Color; + bOverrideSecondaryColour = true; + if (FillMID) FillMID->SetVectorParameterValue(P_SecondaryColour, Color); +} + +void UUIProgressBar_Base::SetSecondaryColourBottom(FLinearColor Color) +{ + SecondaryColourBottom = Color; + bOverrideSecondaryColourBottom = true; + if (FillMID) FillMID->SetVectorParameterValue(P_SecondaryColourBottom, Color); +} + +void UUIProgressBar_Base::SetEmptyColour(FLinearColor Color) +{ + EmptyColour = Color; + bOverrideEmptyColour = true; + if (FillMID) FillMID->SetVectorParameterValue(P_EmptyColour, Color); +} + +void UUIProgressBar_Base::SetGradientEnabled(bool bEnabled) +{ + bEnableGradient = bEnabled; + bOverrideEnableGradient = true; + if (FillMID) FillMID->SetScalarParameterValue(P_EnableGradient, bEnabled ? 1.f : 0.f); +} + +void UUIProgressBar_Base::SetGradientSplit(float Split01) +{ + GradientSplit = FMath::Clamp(Split01, 0.f, 1.f); + bOverrideGradientSplit = true; + if (FillMID) FillMID->SetScalarParameterValue(P_GradientSplit, GradientSplit); +} + +void UUIProgressBar_Base::ApplyOverridesToMID() +{ + if (!FillMID) + { + return; + } + if (bOverridePrimaryColour) FillMID->SetVectorParameterValue(P_PrimaryColour, PrimaryColour); + if (bOverridePrimaryColourBottom) FillMID->SetVectorParameterValue(P_PrimaryColourBottom, PrimaryColourBottom); + if (bOverrideSecondaryColour) FillMID->SetVectorParameterValue(P_SecondaryColour, SecondaryColour); + if (bOverrideSecondaryColourBottom) FillMID->SetVectorParameterValue(P_SecondaryColourBottom, SecondaryColourBottom); + if (bOverrideEmptyColour) FillMID->SetVectorParameterValue(P_EmptyColour, EmptyColour); + if (bOverrideEnableGradient) FillMID->SetScalarParameterValue(P_EnableGradient, bEnableGradient ? 1.f : 0.f); + if (bOverrideGradientSplit) FillMID->SetScalarParameterValue(P_GradientSplit, GradientSplit); +} + +void UUIProgressBar_Base::ShowHealPreview(float TargetPct, FLinearColor PreviewColor) +{ + SetSecondaryColour(PreviewColor); + SetTargetSecondaryLevel(TargetPct); +} + +void UUIProgressBar_Base::ShowDamageLag(float OldPct, float NewPct, FLinearColor LagColor) +{ + SetSecondaryColour(LagColor); + SetSecondaryLevelImmediate(OldPct); + SetPrimaryLevelImmediate(NewPct); + SetTargetSecondaryLevel(NewPct); +} + +void UUIProgressBar_Base::ShowBuffer(float BufferPct, FLinearColor BufferColor) +{ + SetSecondaryColour(BufferColor); + SetTargetSecondaryLevel(FMath::Clamp(CurrentPrimaryLevel + BufferPct, 0.f, 1.f)); +} + +void UUIProgressBar_Base::ClearSecondary() +{ + SetTargetSecondaryLevel(0.f); +} + +void UUIProgressBar_Base::EnsureMID() +{ + if (FillMID || !FillImage) + { + return; + } + UMaterialInterface* Mat = Cast(FillImage->GetBrush().GetResourceObject()); + if (!Mat) + { + return; + } + FillMID = UMaterialInstanceDynamic::Create(Mat, this); + FillImage->SetBrushFromMaterial(FillMID); +} + +void UUIProgressBar_Base::ApplyPrimaryToMID(float Value) +{ + if (FillMID) + { + FillMID->SetScalarParameterValue(P_PrimaryLevel, Value); + } +} + +void UUIProgressBar_Base::ApplySecondaryToMID(float Value) +{ + if (FillMID) + { + FillMID->SetScalarParameterValue(P_SecondaryLevel, Value); + } +} diff --git a/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h b/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h new file mode 100644 index 0000000..aebd7b1 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h @@ -0,0 +1,196 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CommonUserWidget.h" +#include "UIProgressBar_Base.generated.h" + +class UImage; +class UMaterialInstanceDynamic; + +/** + * ProgressBar do ZMMO. Wrappa um UImage com material custom + * (UI_M_ProgressBar) e expõe API com dois níveis: + * Primary (preenchimento atual) + Secondary (preview / damage lag / buffer). + * + * Modelo assimétrico: SecondaryLevel só aparece visualmente se for MAIOR + * que PrimaryLevel — representa heal preview, damage trail, buffer + * empilhado em cima da barra principal. + * + * Cores: por padrão vêm da Material Instance atribuída ao FillImage. + * Cada cor tem um toggle de override (InlineEditConditionToggle) que, + * quando ligado, sobrescreve o valor da MI via MID em PreConstruct. + */ +UCLASS(Abstract, Blueprintable) +class ZMMO_API UUIProgressBar_Base : public UCommonUserWidget +{ + GENERATED_BODY() + +public: + // === Levels === + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar", + meta = (ClampMin = "0", ClampMax = "1")) + float InitialPrimaryLevel = 1.f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar", + meta = (ClampMin = "0", ClampMax = "1")) + float InitialSecondaryLevel = 0.f; + + // === Animation === + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Animation", + meta = (ClampMin = "0", ClampMax = "5")) + float PrimaryAnimationSeconds = 0.25f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Animation", + meta = (ClampMin = "0", ClampMax = "5")) + float SecondaryAnimationSeconds = 0.8f; + + // === Color overrides (sobrescrevem a MI) === + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverridePrimaryColour = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverridePrimaryColour")) + FLinearColor PrimaryColour = FLinearColor(0.957f, 0.769f, 0.353f, 1.f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverridePrimaryColourBottom = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverridePrimaryColourBottom")) + FLinearColor PrimaryColourBottom = FLinearColor(0.478f, 0.282f, 0.094f, 1.f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverrideSecondaryColour = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverrideSecondaryColour")) + FLinearColor SecondaryColour = FLinearColor(0.246201f, 0.002428f, 0.002125f, 1.f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverrideSecondaryColourBottom = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverrideSecondaryColourBottom")) + FLinearColor SecondaryColourBottom = FLinearColor(0.123f, 0.001f, 0.001f, 1.f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverrideEmptyColour = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverrideEmptyColour")) + FLinearColor EmptyColour = FLinearColor(0.f, 0.f, 0.f, 0.6f); + + // === Gradient overrides === + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient", + meta = (InlineEditConditionToggle)) + bool bOverrideEnableGradient = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient", + meta = (EditCondition = "bOverrideEnableGradient")) + bool bEnableGradient = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient", + meta = (InlineEditConditionToggle)) + bool bOverrideGradientSplit = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient", + meta = (EditCondition = "bOverrideGradientSplit", ClampMin = "0", ClampMax = "1")) + float GradientSplit = 0.5f; + + // === Primary API === + + UFUNCTION(BlueprintCallable, Category = "ProgressBar") + void SetTargetPrimaryLevel(float Primary01); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar") + void SetPrimaryLevelImmediate(float Primary01); + + UFUNCTION(BlueprintPure, Category = "ProgressBar") + float GetCurrentPrimaryLevel() const { return CurrentPrimaryLevel; } + + // === Secondary API === + + UFUNCTION(BlueprintCallable, Category = "ProgressBar") + void SetTargetSecondaryLevel(float Sec01); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar") + void SetSecondaryLevelImmediate(float Sec01); + + UFUNCTION(BlueprintPure, Category = "ProgressBar") + float GetCurrentSecondaryLevel() const { return CurrentSecondaryLevel; } + + // === Runtime color/gradient setters (também marcam o override pra evitar reset no próximo PreConstruct) === + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetPrimaryColour(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetPrimaryColourBottom(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetSecondaryColour(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetSecondaryColourBottom(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetEmptyColour(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Gradient") + void SetGradientEnabled(bool bEnabled); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Gradient") + void SetGradientSplit(float Split01); + + // === Wrappers semânticos === + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects") + void ShowHealPreview(float TargetPct, FLinearColor PreviewColor); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects") + void ShowDamageLag(float OldPct, float NewPct, FLinearColor LagColor); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects") + void ShowBuffer(float BufferPct, FLinearColor BufferColor); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects") + void ClearSecondary(); + +protected: + virtual void NativePreConstruct() override; + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UPROPERTY(meta = (BindWidget)) + TObjectPtr FillImage; + +private: + UPROPERTY(Transient) + TObjectPtr FillMID; + + void EnsureMID(); + void ApplyPrimaryToMID(float Value); + void ApplySecondaryToMID(float Value); + void ApplyOverridesToMID(); + + float CurrentPrimaryLevel = 1.f; + float TargetPrimaryLevel = 1.f; + float PrimaryStart = 1.f; + float PrimaryElapsed = 0.f; + bool bAnimatingPrimary = false; + + float CurrentSecondaryLevel = 0.f; + float TargetSecondaryLevel = 0.f; + float SecondaryStart = 0.f; + float SecondaryElapsed = 0.f; + bool bAnimatingSecondary = false; +};