718 lines
20 KiB
C++
718 lines
20 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SActorCanvas.h"
|
|
#include "Layout/ArrangedChildren.h"
|
|
#include "Rendering/DrawElements.h"
|
|
#include "Widgets/SLeafWidget.h"
|
|
#include "IActorIndicatorWidget.h"
|
|
#include "LyraIndicatorManagerComponent.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
|
|
namespace EArrowDirection
|
|
{
|
|
enum Type
|
|
{
|
|
Left,
|
|
Top,
|
|
Right,
|
|
Bottom,
|
|
MAX
|
|
};
|
|
}
|
|
|
|
// Angles for the direction of the arrow to display
|
|
const float ArrowRotations[EArrowDirection::MAX] =
|
|
{
|
|
270.0f,
|
|
0.0f,
|
|
90.0f,
|
|
180.0f
|
|
};
|
|
|
|
// Offsets for the each direction that the arrow can point
|
|
const FVector2D ArrowOffsets[EArrowDirection::MAX] =
|
|
{
|
|
FVector2D(-1.0f, 0.0f),
|
|
FVector2D(0.0f, -1.0f),
|
|
FVector2D(1.0f, 0.0f),
|
|
FVector2D(0.0f, 1.0f)
|
|
};
|
|
|
|
|
|
class SActorCanvasArrowWidget : public SLeafWidget
|
|
{
|
|
public:
|
|
|
|
SLATE_BEGIN_ARGS(SActorCanvasArrowWidget)
|
|
{}
|
|
/** always goes at the end */
|
|
SLATE_END_ARGS()
|
|
|
|
/** Ctor */
|
|
SActorCanvasArrowWidget()
|
|
: Rotation(0.0f)
|
|
, Arrow(nullptr)
|
|
{
|
|
|
|
}
|
|
|
|
/** Every widget needs one of these */
|
|
void Construct(const FArguments& InArgs, const FSlateBrush* ActorCanvasArrowBrush)
|
|
{
|
|
Arrow = ActorCanvasArrowBrush;
|
|
SetCanTick(false);
|
|
}
|
|
|
|
virtual int32 OnPaint(const FPaintArgs& Args,
|
|
const FGeometry& AllottedGeometry,
|
|
const FSlateRect& MyClippingRect,
|
|
FSlateWindowElementList& OutDrawElements,
|
|
int32 LayerId,
|
|
const FWidgetStyle& InWidgetStyle,
|
|
bool bParentEnabled) const override
|
|
{
|
|
int32 MaxLayerId = LayerId;
|
|
|
|
if (Arrow)
|
|
{
|
|
const bool bIsEnabled = ShouldBeEnabled(bParentEnabled);
|
|
const ESlateDrawEffect DrawEffects = bIsEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
const FColor FinalColorAndOpacity = (InWidgetStyle.GetColorAndOpacityTint() * Arrow->GetTint(InWidgetStyle)).ToFColor(true);
|
|
|
|
FSlateDrawElement::MakeRotatedBox(
|
|
OutDrawElements,
|
|
MaxLayerId++,
|
|
AllottedGeometry.ToPaintGeometry(FVector2D::ZeroVector, Arrow->ImageSize, 1.f),
|
|
Arrow,
|
|
DrawEffects,
|
|
FMath::DegreesToRadians(GetRotation()),
|
|
TOptional<FVector2D>(),
|
|
FSlateDrawElement::RelativeToElement,
|
|
FinalColorAndOpacity
|
|
);
|
|
}
|
|
|
|
return MaxLayerId;
|
|
}
|
|
|
|
FORCEINLINE void SetRotation(float InRotation)
|
|
{
|
|
Rotation = FMath::Fmod(InRotation, 360.0f);
|
|
}
|
|
|
|
FORCEINLINE float GetRotation() const
|
|
{
|
|
return Rotation;
|
|
}
|
|
|
|
virtual FVector2D ComputeDesiredSize(float) const override
|
|
{
|
|
if (Arrow)
|
|
{
|
|
return Arrow->ImageSize;
|
|
}
|
|
else
|
|
{
|
|
return FVector2D::ZeroVector;
|
|
}
|
|
}
|
|
|
|
private:
|
|
float Rotation;
|
|
|
|
const FSlateBrush* Arrow;
|
|
};
|
|
|
|
void SActorCanvas::Construct(const FArguments& InArgs, const FLocalPlayerContext& InLocalPlayerContext, const FSlateBrush* InActorCanvasArrowBrush)
|
|
{
|
|
LocalPlayerContext = InLocalPlayerContext;
|
|
ActorCanvasArrowBrush = InActorCanvasArrowBrush;
|
|
|
|
IndicatorPool.SetWorld(LocalPlayerContext.GetWorld());
|
|
|
|
SetCanTick(false);
|
|
SetVisibility(EVisibility::SelfHitTestInvisible);
|
|
|
|
// Create 10 arrows for starters
|
|
for (int32 i = 0; i < 10; ++i)
|
|
{
|
|
TSharedRef<SActorCanvasArrowWidget> ArrowWidget = SNew(SActorCanvasArrowWidget, ActorCanvasArrowBrush);
|
|
ArrowWidget->SetVisibility(EVisibility::Collapsed);
|
|
|
|
ArrowChildren.AddSlot(MoveTemp(
|
|
FArrowSlot::FSlotArguments(MakeUnique<FArrowSlot>())
|
|
[
|
|
ArrowWidget
|
|
]
|
|
));
|
|
}
|
|
|
|
UpdateActiveTimer();
|
|
}
|
|
|
|
EActiveTimerReturnType SActorCanvas::UpdateCanvas(double InCurrentTime, float InDeltaTime)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_SActorCanvas_UpdateCanvas);
|
|
|
|
if (!OptionalPaintGeometry.IsSet())
|
|
{
|
|
return EActiveTimerReturnType::Continue;
|
|
}
|
|
|
|
// Grab the local player
|
|
ULocalPlayer* LocalPlayer = LocalPlayerContext.GetLocalPlayer();
|
|
ULyraIndicatorManagerComponent* IndicatorComponent = IndicatorComponentPtr.Get();
|
|
if (IndicatorComponent == nullptr)
|
|
{
|
|
IndicatorComponent = ULyraIndicatorManagerComponent::GetComponent(LocalPlayerContext.GetPlayerController());
|
|
if (IndicatorComponent)
|
|
{
|
|
IndicatorComponentPtr = IndicatorComponent;
|
|
IndicatorComponent->OnIndicatorAdded.AddSP(this, &SActorCanvas::OnIndicatorAdded);
|
|
IndicatorComponent->OnIndicatorRemoved.AddSP(this, &SActorCanvas::OnIndicatorRemoved);
|
|
for (UIndicatorDescriptor* Indicator : IndicatorComponent->GetIndicators())
|
|
{
|
|
OnIndicatorAdded(Indicator);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//TODO HIDE EVERYTHING
|
|
return EActiveTimerReturnType::Continue;
|
|
}
|
|
}
|
|
|
|
//Make sure we have a player. If we don't, we can't project anything
|
|
if (LocalPlayer)
|
|
{
|
|
const FGeometry PaintGeometry = OptionalPaintGeometry.GetValue();
|
|
|
|
FSceneViewProjectionData ProjectionData;
|
|
if (LocalPlayer->GetProjectionData(LocalPlayer->ViewportClient->Viewport, /*out*/ ProjectionData))
|
|
{
|
|
SetShowAnyIndicators(true);
|
|
|
|
bool IndicatorsChanged = false;
|
|
|
|
for (int32 ChildIndex = 0; ChildIndex < CanvasChildren.Num(); ++ChildIndex)
|
|
{
|
|
SActorCanvas::FSlot& CurChild = CanvasChildren[ChildIndex];
|
|
UIndicatorDescriptor* Indicator = CurChild.Indicator;
|
|
|
|
// If the slot content is invalid and we have permission to remove it
|
|
if (Indicator->CanAutomaticallyRemove())
|
|
{
|
|
IndicatorsChanged = true;
|
|
|
|
RemoveIndicatorForEntry(Indicator);
|
|
// Decrement the current index to account for the removal
|
|
--ChildIndex;
|
|
continue;
|
|
}
|
|
|
|
CurChild.SetIsIndicatorVisible(Indicator->GetIsVisible());
|
|
|
|
if (!CurChild.GetIsIndicatorVisible())
|
|
{
|
|
IndicatorsChanged |= CurChild.bIsDirty();
|
|
CurChild.ClearDirtyFlag();
|
|
continue;
|
|
}
|
|
|
|
// If the indicator changed clamp status between updates, alert the indicator and mark the indicators as changed
|
|
if (CurChild.WasIndicatorClampedStatusChanged())
|
|
{
|
|
//Indicator->OnIndicatorClampedStatusChanged(CurChild.WasIndicatorClamped());
|
|
CurChild.ClearIndicatorClampedStatusChangedFlag();
|
|
IndicatorsChanged = true;
|
|
}
|
|
|
|
FVector ScreenPositionWithDepth;
|
|
|
|
FIndicatorProjection Projector;
|
|
const bool Success = Projector.Project(*Indicator, ProjectionData, PaintGeometry.Size, OUT ScreenPositionWithDepth);
|
|
|
|
if (!Success)
|
|
{
|
|
CurChild.SetHasValidScreenPosition(false);
|
|
CurChild.SetInFrontOfCamera(false);
|
|
|
|
IndicatorsChanged |= CurChild.bIsDirty();
|
|
CurChild.ClearDirtyFlag();
|
|
continue;
|
|
}
|
|
|
|
CurChild.SetInFrontOfCamera(Success);
|
|
CurChild.SetHasValidScreenPosition(CurChild.GetInFrontOfCamera() || Indicator->GetClampToScreen());
|
|
|
|
if (CurChild.HasValidScreenPosition())
|
|
{
|
|
// Only dirty the screen position if we can actually show this indicator.
|
|
CurChild.SetScreenPosition(FVector2D(ScreenPositionWithDepth));
|
|
CurChild.SetDepth(ScreenPositionWithDepth.X);
|
|
}
|
|
|
|
CurChild.SetPriority(Indicator->GetPriority());
|
|
|
|
IndicatorsChanged |= CurChild.bIsDirty();
|
|
CurChild.ClearDirtyFlag();
|
|
}
|
|
|
|
if (IndicatorsChanged)
|
|
{
|
|
Invalidate(EInvalidateWidget::Paint);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetShowAnyIndicators(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetShowAnyIndicators(false);
|
|
}
|
|
|
|
if (AllIndicators.Num() == 0)
|
|
{
|
|
TickHandle.Reset();
|
|
return EActiveTimerReturnType::Stop;
|
|
}
|
|
else
|
|
{
|
|
return EActiveTimerReturnType::Continue;
|
|
}
|
|
}
|
|
|
|
void SActorCanvas::SetShowAnyIndicators(bool bIndicators)
|
|
{
|
|
if (bShowAnyIndicators != bIndicators)
|
|
{
|
|
bShowAnyIndicators = bIndicators;
|
|
|
|
if (!bShowAnyIndicators)
|
|
{
|
|
for (int32 ChildIndex = 0; ChildIndex < AllChildren.Num(); ChildIndex++)
|
|
{
|
|
AllChildren.GetChildAt(ChildIndex)->SetVisibility(EVisibility::Collapsed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SActorCanvas::OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_SActorCanvas_OnArrangeChildren);
|
|
|
|
NextArrowIndex = 0;
|
|
|
|
//Make sure we have a player. If we don't, we can't project anything
|
|
if (bShowAnyIndicators)
|
|
{
|
|
const FVector2D ArrowWidgetSize = ActorCanvasArrowBrush->GetImageSize();
|
|
const FIntPoint FixedPadding = FIntPoint(10.0f, 10.0f) + FIntPoint(ArrowWidgetSize.X, ArrowWidgetSize.Y);
|
|
const FVector Center = FVector(AllottedGeometry.Size * 0.5f, 0.0f);
|
|
|
|
// Sort the children
|
|
TArray<const SActorCanvas::FSlot*> SortedSlots;
|
|
for (int32 ChildIndex = 0; ChildIndex < CanvasChildren.Num(); ++ChildIndex)
|
|
{
|
|
SortedSlots.Add(&CanvasChildren[ChildIndex]);
|
|
}
|
|
|
|
SortedSlots.StableSort([](const SActorCanvas::FSlot& A, const SActorCanvas::FSlot& B)
|
|
{
|
|
return A.GetPriority() == B.GetPriority() ? A.GetDepth() > B.GetDepth() : A.GetPriority() < B.GetPriority();
|
|
});
|
|
|
|
// Go through all the sorted children
|
|
for (int32 ChildIndex = 0; ChildIndex < SortedSlots.Num(); ++ChildIndex)
|
|
{
|
|
//grab a child
|
|
const SActorCanvas::FSlot& CurChild = *SortedSlots[ChildIndex];
|
|
const UIndicatorDescriptor* Indicator = CurChild.Indicator;
|
|
|
|
// Skip this indicator if it's invalid or has an invalid world position
|
|
if (!ArrangedChildren.Accepts(CurChild.GetWidget()->GetVisibility()))
|
|
{
|
|
CurChild.SetWasIndicatorClamped(false);
|
|
continue;
|
|
}
|
|
|
|
FVector2D ScreenPosition = CurChild.GetScreenPosition();
|
|
const bool bInFrontOfCamera = CurChild.GetInFrontOfCamera();
|
|
|
|
// Don't bother if we can't project the position and the indicator doesn't want to be clamped
|
|
const bool bShouldClamp = Indicator->GetClampToScreen();
|
|
|
|
//get the offset and final size of the slot
|
|
FVector2D SlotSize, SlotOffset, SlotPaddingMin, SlotPaddingMax;
|
|
GetOffsetAndSize(Indicator, SlotSize, SlotOffset, SlotPaddingMin, SlotPaddingMax);
|
|
|
|
bool bWasIndicatorClamped = false;
|
|
|
|
// If we don't have to clamp this thing, we can skip a lot of work
|
|
if (bShouldClamp)
|
|
{
|
|
//figure out if we clamped to any edge of the screen
|
|
EArrowDirection::Type ClampDir = EArrowDirection::MAX;
|
|
|
|
// Determine the size of inner screen rect to clamp within
|
|
const FIntPoint RectMin = FIntPoint(SlotPaddingMin.X, SlotPaddingMin.Y) + FixedPadding;
|
|
const FIntPoint RectMax = FIntPoint(AllottedGeometry.Size.X - SlotPaddingMax.X, AllottedGeometry.Size.Y - SlotPaddingMax.Y) - FixedPadding;
|
|
const FIntRect ClampRect(RectMin, RectMax);
|
|
|
|
// Make sure the screen position is within the clamp rect
|
|
if (!ClampRect.Contains(FIntPoint(ScreenPosition.X, ScreenPosition.Y)))
|
|
{
|
|
const FPlane Planes[] =
|
|
{
|
|
FPlane(FVector(1.0f, 0.0f, 0.0f), ClampRect.Min.X), // Left
|
|
FPlane(FVector(0.0f, 1.0f, 0.0f), ClampRect.Min.Y), // Top
|
|
FPlane(FVector(-1.0f, 0.0f, 0.0f), -ClampRect.Max.X), // Right
|
|
FPlane(FVector(0.0f, -1.0f, 0.0f), -ClampRect.Max.Y) // Bottom
|
|
};
|
|
|
|
for (int32 i = 0; i < EArrowDirection::MAX; ++i)
|
|
{
|
|
FVector NewPoint;
|
|
if (FMath::SegmentPlaneIntersection(Center, FVector(ScreenPosition, 0.0f), Planes[i], NewPoint))
|
|
{
|
|
ClampDir = (EArrowDirection::Type)i;
|
|
ScreenPosition = FVector2D(NewPoint);
|
|
}
|
|
}
|
|
}
|
|
else if (!bInFrontOfCamera)
|
|
{
|
|
const float ScreenXNorm = ScreenPosition.X / (RectMax.X - RectMin.X);
|
|
const float ScreenYNorm = ScreenPosition.Y / (RectMax.Y - RectMin.Y);
|
|
//we need to pin this thing to the side of the screen
|
|
if (ScreenXNorm < ScreenYNorm)
|
|
{
|
|
if (ScreenXNorm < (-ScreenYNorm + 1.0f))
|
|
{
|
|
ClampDir = EArrowDirection::Left;
|
|
ScreenPosition.X = ClampRect.Min.X;
|
|
}
|
|
else
|
|
{
|
|
ClampDir = EArrowDirection::Bottom;
|
|
ScreenPosition.Y = ClampRect.Max.Y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ScreenXNorm < (-ScreenYNorm + 1.0f))
|
|
{
|
|
ClampDir = EArrowDirection::Top;
|
|
ScreenPosition.Y = ClampRect.Min.Y;
|
|
}
|
|
else
|
|
{
|
|
ClampDir = EArrowDirection::Right;
|
|
ScreenPosition.X = ClampRect.Max.X;
|
|
}
|
|
}
|
|
}
|
|
|
|
bWasIndicatorClamped = (ClampDir != EArrowDirection::MAX);
|
|
|
|
// should we show an arrow
|
|
if (Indicator->GetShowClampToScreenArrow() &&
|
|
bWasIndicatorClamped &&
|
|
ArrowChildren.IsValidIndex(NextArrowIndex))
|
|
{
|
|
const FVector2D ArrowOffsetDirection = ArrowOffsets[ClampDir];
|
|
const float ArrowRotation = ArrowRotations[ClampDir];
|
|
|
|
//grab an arrow widget
|
|
TSharedRef<SActorCanvasArrowWidget> ArrowWidgetToUse = StaticCastSharedRef<SActorCanvasArrowWidget>(ArrowChildren.GetChildAt(NextArrowIndex));
|
|
NextArrowIndex++;
|
|
|
|
//set the rotation of the arrow
|
|
ArrowWidgetToUse->SetRotation(ArrowRotation);
|
|
|
|
//figure out the magnitude of the offset
|
|
const FVector2D OffsetMagnitude = (SlotSize + ArrowWidgetSize) * 0.5f;
|
|
|
|
//used to center the arrow on the position
|
|
const FVector2D ArrowCenteringOffset = -(ArrowWidgetSize * 0.5f);
|
|
|
|
FVector2D ArrowAlignmentOffset = FVector2D::ZeroVector;
|
|
switch (Indicator->VAlignment)
|
|
{
|
|
case VAlign_Top:
|
|
ArrowAlignmentOffset = SlotSize * FVector2D(0.0f, 0.5f);
|
|
break;
|
|
case VAlign_Bottom:
|
|
ArrowAlignmentOffset = SlotSize * FVector2D(0.0f, -0.5f);
|
|
break;
|
|
}
|
|
|
|
//figure out the offset for the arrow
|
|
const FVector2D WidgetOffset = (OffsetMagnitude * ArrowOffsetDirection);
|
|
|
|
const FVector2D FinalOffset = (WidgetOffset + ArrowAlignmentOffset + ArrowCenteringOffset);
|
|
|
|
//get the final position
|
|
const FVector2D FinalPosition = (ScreenPosition + FinalOffset);
|
|
|
|
ArrowWidgetToUse->SetVisibility(EVisibility::HitTestInvisible);
|
|
|
|
// Inject the arrow on top of the indicator
|
|
ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(
|
|
ArrowWidgetToUse, // The child widget being arranged
|
|
FinalPosition, // Child's local position (i.e. position within parent)
|
|
ArrowWidgetSize, // Child's size
|
|
1.f // Child's scale
|
|
));
|
|
}
|
|
}
|
|
|
|
CurChild.SetWasIndicatorClamped(bWasIndicatorClamped);
|
|
|
|
// Add the information about this child to the output list (ArrangedChildren)
|
|
ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(
|
|
CurChild.GetWidget(),
|
|
ScreenPosition + SlotOffset,
|
|
SlotSize,
|
|
1.f
|
|
));
|
|
}
|
|
}
|
|
|
|
if (NextArrowIndex < ArrowIndexLastUpdate)
|
|
{
|
|
for (int32 ArrowRemovedIndex = NextArrowIndex; ArrowRemovedIndex < ArrowIndexLastUpdate; ArrowRemovedIndex++)
|
|
{
|
|
ArrowChildren.GetChildAt(ArrowRemovedIndex)->SetVisibility(EVisibility::Collapsed);
|
|
}
|
|
}
|
|
|
|
ArrowIndexLastUpdate = NextArrowIndex;
|
|
}
|
|
|
|
int32 SActorCanvas::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_SActorCanvas_OnPaint);
|
|
|
|
OptionalPaintGeometry = AllottedGeometry;
|
|
|
|
FArrangedChildren ArrangedChildren(EVisibility::Visible);
|
|
ArrangeChildren(AllottedGeometry, ArrangedChildren);
|
|
|
|
int32 MaxLayerId = LayerId;
|
|
|
|
const FPaintArgs NewArgs = Args.WithNewParent(this);
|
|
const bool bShouldBeEnabled = ShouldBeEnabled(bParentEnabled);
|
|
|
|
for (const FArrangedWidget& CurWidget : ArrangedChildren.GetInternalArray())
|
|
{
|
|
if (!IsChildWidgetCulled(MyCullingRect, CurWidget))
|
|
{
|
|
SWidget* MutableWidget = const_cast<SWidget*>(&CurWidget.Widget.Get());
|
|
|
|
const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint(NewArgs, CurWidget.Geometry, MyCullingRect, OutDrawElements, bDrawElementsInOrder ? MaxLayerId : LayerId, InWidgetStyle, bShouldBeEnabled);
|
|
MaxLayerId = FMath::Max(MaxLayerId, CurWidgetsMaxLayerId);
|
|
}
|
|
else
|
|
{
|
|
//SlateGI - RemoveContent
|
|
}
|
|
}
|
|
|
|
return MaxLayerId;
|
|
}
|
|
|
|
FString SActorCanvas::GetReferencerName() const
|
|
{
|
|
return TEXT("SActorCanvas");
|
|
}
|
|
|
|
void SActorCanvas::AddReferencedObjects( FReferenceCollector& Collector )
|
|
{
|
|
Collector.AddReferencedObjects(AllIndicators);
|
|
}
|
|
|
|
void SActorCanvas::OnIndicatorAdded(UIndicatorDescriptor* Indicator)
|
|
{
|
|
AllIndicators.Add(Indicator);
|
|
InactiveIndicators.Add(Indicator);
|
|
|
|
AddIndicatorForEntry(Indicator);
|
|
}
|
|
|
|
void SActorCanvas::OnIndicatorRemoved(UIndicatorDescriptor* Indicator)
|
|
{
|
|
RemoveIndicatorForEntry(Indicator);
|
|
|
|
AllIndicators.Remove(Indicator);
|
|
InactiveIndicators.Remove(Indicator);
|
|
}
|
|
|
|
void SActorCanvas::AddIndicatorForEntry(UIndicatorDescriptor* Indicator)
|
|
{
|
|
// Async load the indicator, and pool the results so that it's easy to use and reuse the widgets.
|
|
TSoftClassPtr<UUserWidget> IndicatorClass = Indicator->GetIndicatorClass();
|
|
if (!IndicatorClass.IsNull())
|
|
{
|
|
TWeakObjectPtr<UIndicatorDescriptor> IndicatorPtr(Indicator);
|
|
AsyncLoad(IndicatorClass, [this, IndicatorPtr, IndicatorClass]() {
|
|
if (UIndicatorDescriptor* Indicator = IndicatorPtr.Get())
|
|
{
|
|
// While async loading this indicator widget we could have removed it.
|
|
if (!AllIndicators.Contains(Indicator))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Create the widget from the pool.
|
|
if (UUserWidget* IndicatorWidget = IndicatorPool.GetOrCreateInstance(TSubclassOf<UUserWidget>(IndicatorClass.Get())))
|
|
{
|
|
if (IndicatorWidget->GetClass()->ImplementsInterface(UIndicatorWidgetInterface::StaticClass()))
|
|
{
|
|
IIndicatorWidgetInterface::Execute_BindIndicator(IndicatorWidget, Indicator);
|
|
}
|
|
|
|
Indicator->IndicatorWidget = IndicatorWidget;
|
|
|
|
InactiveIndicators.Remove(Indicator);
|
|
|
|
AddActorSlot(Indicator)
|
|
[
|
|
SAssignNew(Indicator->CanvasHost, SBox)
|
|
[
|
|
IndicatorWidget->TakeWidget()
|
|
]
|
|
];
|
|
}
|
|
}
|
|
});
|
|
StartAsyncLoading();
|
|
}
|
|
}
|
|
|
|
void SActorCanvas::RemoveIndicatorForEntry(UIndicatorDescriptor* Indicator)
|
|
{
|
|
if (UUserWidget* IndicatorWidget = Indicator->IndicatorWidget.Get())
|
|
{
|
|
if (IndicatorWidget->GetClass()->ImplementsInterface(UIndicatorWidgetInterface::StaticClass()))
|
|
{
|
|
IIndicatorWidgetInterface::Execute_UnbindIndicator(IndicatorWidget, Indicator);
|
|
}
|
|
|
|
Indicator->IndicatorWidget = nullptr;
|
|
|
|
IndicatorPool.Release(IndicatorWidget);
|
|
}
|
|
|
|
TSharedPtr<SWidget> CanvasHost = Indicator->CanvasHost.Pin();
|
|
if (CanvasHost.IsValid())
|
|
{
|
|
RemoveActorSlot(CanvasHost.ToSharedRef());
|
|
Indicator->CanvasHost.Reset();
|
|
}
|
|
}
|
|
|
|
SActorCanvas::FScopedWidgetSlotArguments SActorCanvas::AddActorSlot(UIndicatorDescriptor* Indicator)
|
|
{
|
|
TWeakPtr<SActorCanvas> WeakCanvas = SharedThis(this);
|
|
return FScopedWidgetSlotArguments{ MakeUnique<FSlot>(Indicator), this->CanvasChildren, INDEX_NONE
|
|
, [WeakCanvas](const FSlot*, int32)
|
|
{
|
|
if (TSharedPtr<SActorCanvas> Canvas = WeakCanvas.Pin())
|
|
{
|
|
Canvas->UpdateActiveTimer();
|
|
}
|
|
}};
|
|
}
|
|
|
|
int32 SActorCanvas::RemoveActorSlot(const TSharedRef<SWidget>& SlotWidget)
|
|
{
|
|
for (int32 SlotIdx = 0; SlotIdx < CanvasChildren.Num(); ++SlotIdx)
|
|
{
|
|
if ( SlotWidget == CanvasChildren[SlotIdx].GetWidget() )
|
|
{
|
|
CanvasChildren.RemoveAt(SlotIdx);
|
|
|
|
UpdateActiveTimer();
|
|
|
|
return SlotIdx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void SActorCanvas::GetOffsetAndSize(const UIndicatorDescriptor* Indicator,
|
|
FVector2D& OutSize,
|
|
FVector2D& OutOffset,
|
|
FVector2D& OutPaddingMin,
|
|
FVector2D& OutPaddingMax) const
|
|
{
|
|
//This might get used one day
|
|
FVector2D AllottedSize = FVector2D::ZeroVector;
|
|
|
|
//grab the desired size of the child widget
|
|
TSharedPtr<SWidget> CanvasHost = Indicator->CanvasHost.Pin();
|
|
if (CanvasHost.IsValid())
|
|
{
|
|
OutSize = CanvasHost->GetDesiredSize();
|
|
}
|
|
|
|
//handle horizontal alignment
|
|
switch(Indicator->GetHAlign())
|
|
{
|
|
case HAlign_Left: // same as Align_Top
|
|
OutOffset.X = 0.0f;
|
|
OutPaddingMin.X = 0.0f;
|
|
OutPaddingMax.X = OutSize.X;
|
|
break;
|
|
|
|
case HAlign_Center:
|
|
OutOffset.X = (AllottedSize.X - OutSize.X) / 2.0f;
|
|
OutPaddingMin.X = OutSize.X / 2.0f;
|
|
OutPaddingMax.X = OutPaddingMin.X;
|
|
break;
|
|
|
|
case HAlign_Right: // same as Align_Bottom
|
|
OutOffset.X = AllottedSize.X - OutSize.X;
|
|
OutPaddingMin.X = OutSize.X;
|
|
OutPaddingMax.X = 0.0f;
|
|
break;
|
|
}
|
|
|
|
//Now, handle vertical alignment
|
|
switch(Indicator->GetVAlign())
|
|
{
|
|
case VAlign_Top:
|
|
OutOffset.Y = 0.0f;
|
|
OutPaddingMin.Y = 0.0f;
|
|
OutPaddingMax.Y = OutSize.Y;
|
|
break;
|
|
|
|
case VAlign_Center:
|
|
OutOffset.Y = (AllottedSize.Y - OutSize.Y) / 2.0f;
|
|
OutPaddingMin.Y = OutSize.Y / 2.0f;
|
|
OutPaddingMax.Y = OutPaddingMin.Y;
|
|
break;
|
|
|
|
case VAlign_Bottom:
|
|
OutOffset.Y = AllottedSize.Y - OutSize.Y;
|
|
OutPaddingMin.Y = OutSize.Y;
|
|
OutPaddingMax.Y = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SActorCanvas::UpdateActiveTimer()
|
|
{
|
|
const bool NeedsTicks = AllIndicators.Num() > 0 || !IndicatorComponentPtr.IsValid();
|
|
|
|
if (NeedsTicks && !TickHandle.IsValid())
|
|
{
|
|
TickHandle = RegisterActiveTimer(0, FWidgetActiveTimerDelegate::CreateSP(this, &SActorCanvas::UpdateCanvas));
|
|
}
|
|
}
|