Added Tobii from 4.23 + updated

This commit is contained in:
2023-03-04 04:19:09 +11:00
parent 2d5b9008e5
commit 1cde391516
521 changed files with 11735 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "STobiiGazeFocusableWidget.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "Engine/Canvas.h"
#include "Engine/Engine.h"
#include "Components/WidgetComponent.h"
#include "HAL/IConsoleManager.h"
#include "Slate/SceneViewport.h"
static TAutoConsoleVariable<int32> CVarEnableCleanUI(TEXT("tobii.EnableCleanUI"), 1, TEXT("0 - CleanUI is disabled. 1 - CleanUI is enabled."));
static TAutoConsoleVariable<float> CVarCleanUIFadeInTimeSecs(TEXT("tobii.CleanUIFadeInTimeSecs"), 0.0f, TEXT("We want the fade in time to be fairly fast so that the information is instantly available to the user."));
static TAutoConsoleVariable<float> CVarCleanUIFadeOutTimeSecs(TEXT("tobii.CleanUIFadeOutTimeSecs"), 1.8f, TEXT("The fade out time should be fairly slow however to make sure that it doesn't draw peripheral vision attention."));
static TAutoConsoleVariable<float> CVarCleanUIMinAlpha(TEXT("tobii.CleanUIMinAlpha"), 0.3f, TEXT("The clean UI won't fade something out beyond this minimum alpha."));
static TAutoConsoleVariable<float> CVarCleanUIMaxAlpha(TEXT("tobii.CleanUIMaxAlpha"), 1.0f, TEXT("Change this if you don't want the clean UI to increase alpha beyond this point"));
STobiiGazeFocusableWidget::STobiiGazeFocusableWidget()
: CleanUIAlpha(1.0f)
, CleanUIContainersToPollHitsFrom()
{
bCanSupportFocus = false;
SetCanTick(true);
}
void STobiiGazeFocusableWidget::Construct(const FArguments& InArgs)
{
SBox::FArguments ParentArgs;
ParentArgs._HAlign = InArgs._HAlign;
ParentArgs._VAlign = InArgs._VAlign;
ParentArgs._Padding = InArgs._Padding;
ParentArgs._Content = InArgs._Content;
SBox::Construct(ParentArgs);
OnHovered = InArgs._OnHovered;
OnUnhovered = InArgs._OnUnhovered;
}
bool STobiiGazeFocusableWidget::HitByGaze()
{
return UMGWidget.IsValid() && (UMGWidget->CleanUIMode == ETobiiCleanUIMode::FocusExclusive ?
UMGWidget->HasFocus() : UMGWidget->IsInFocusCollection());
}
void STobiiGazeFocusableWidget::AddExtraCleanUIContainerToPollHitsFrom(STobiiGazeFocusableWidget* CleanUIContainerToPoll, bool PollGazeHits /*= true*/, bool PollMouseHits /*= true*/)
{
if (CleanUIContainerToPoll != nullptr)
{
CleanUIPollingInfo PollingInfo;
PollingInfo.CleanUIToPollFrom = TWeakPtr<SWidget>(CleanUIContainerToPoll->AsShared());
PollingInfo.PollGaze = PollGazeHits;
PollingInfo.PollPointer = PollMouseHits;
CleanUIContainersToPollHitsFrom.Add(PollingInfo);
}
}
void STobiiGazeFocusableWidget::RemoveExtraCleanUIContainerToPollHitsFrom(STobiiGazeFocusableWidget* CleanUIContainerToStopPolling)
{
if (CleanUIContainerToStopPolling != nullptr)
{
CleanUIContainersToPollHitsFrom.RemoveAll([=](CleanUIPollingInfo& CurCleanUIPollingInfo)
{
return !CurCleanUIPollingInfo.CleanUIToPollFrom.IsValid() || CurCleanUIPollingInfo.CleanUIToPollFrom.HasSameObject(CleanUIContainerToStopPolling);
});
}
}
void STobiiGazeFocusableWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
if (!UMGWidget.IsValid()
|| UMGWidget->CleanUIMode == ETobiiCleanUIMode::Disabled
|| GEngine == nullptr
|| GEngine->GameViewport == nullptr
|| GEngine->GameViewport->GetGameViewport() == nullptr
|| GetChildren()->Num() == 0)
{
CleanUIAlpha = 1.0f;
return;
}
bool bTriggerCleanUIOnMouseOver = UMGWidget.IsValid() && UMGWidget->bTriggerCleanUIOnMouseOver;
bool bHitByGaze = HitByGaze();
bool bShouldFadeIn = bHitByGaze || (bTriggerCleanUIOnMouseOver && bIsHovered);
// Check if we have any hits on our CleanUIContainersToPollHitsFrom to see if we should still fade in even though we still think we should fade out
if (!bShouldFadeIn)
{
for (CleanUIPollingInfo& PollingInfo : CleanUIContainersToPollHitsFrom)
{
if (PollingInfo.CleanUIToPollFrom.IsValid())
{
TSharedPtr<SWidget> CleanUIWidget = PollingInfo.CleanUIToPollFrom.Pin();
STobiiGazeFocusableWidget* CleanUIToPollFrom = (STobiiGazeFocusableWidget*)CleanUIWidget.Get();
if ((PollingInfo.PollGaze && CleanUIToPollFrom->HitByGaze())
|| PollingInfo.PollPointer && bTriggerCleanUIOnMouseOver && CleanUIToPollFrom->bIsHovered)
{
bShouldFadeIn = true;
break;
}
}
}
}
UMGWidget->TimeCleanUIIsSuppressedForSecs = FMath::Max(UMGWidget->TimeCleanUIIsSuppressedForSecs -= InDeltaTime, 0.0f);
bool bCleanUISuppressed = UMGWidget->TimeCleanUIIsSuppressedForSecs > FLT_EPSILON;
bShouldFadeIn = bShouldFadeIn || bCleanUISuppressed;
float MinAlpha = UMGWidget->CleanUIMinAlphaOverride >= 0.0f ? UMGWidget->CleanUIMinAlphaOverride : CVarCleanUIMinAlpha.GetValueOnAnyThread();
float MaxAlpha = UMGWidget->CleanUIMaxAlphaOverride >= 0.0f ? UMGWidget->CleanUIMaxAlphaOverride : CVarCleanUIMaxAlpha.GetValueOnAnyThread();
if (bShouldFadeIn)
{
float FadeInTime = UMGWidget->CleanUIFadeInTimeSecsOverride >= 0.0f ? UMGWidget->CleanUIFadeInTimeSecsOverride : CVarCleanUIFadeInTimeSecs.GetValueOnAnyThread();
CleanUIAlpha = FadeInTime > 0.0f ? FMath::Clamp(CleanUIAlpha + InDeltaTime / FadeInTime, MinAlpha, MaxAlpha) : MaxAlpha;
}
else
{
float FadeOutTime = UMGWidget->CleanUIFadeOutTimeSecsOverride >= 0.0f ? UMGWidget->CleanUIFadeOutTimeSecsOverride : CVarCleanUIFadeOutTimeSecs.GetValueOnAnyThread();
CleanUIAlpha = FadeOutTime > 0.0f ? FMath::Clamp(CleanUIAlpha - InDeltaTime / FadeOutTime, MinAlpha, MaxAlpha) : MinAlpha;
}
}
int32 STobiiGazeFocusableWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
const float Alpha = UMGWidget.IsValid() ? (UMGWidget->CleanUIMode == ETobiiCleanUIMode::Silent ? 1.0f : CleanUIAlpha) : 1.0f;
FWidgetStyle CompoundedWidgetStyle = FWidgetStyle(InWidgetStyle).BlendColorAndOpacityTint(FLinearColor(1.0f, 1.0f, 1.0f, Alpha));
return SBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, CompoundedWidgetStyle, bParentEnabled);
}
void STobiiGazeFocusableWidget::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
SWidget::OnMouseEnter(MyGeometry, MouseEvent);
OnHovered.ExecuteIfBound();
Invalidate(EInvalidateWidget::Layout);
}
void STobiiGazeFocusableWidget::OnMouseLeave(const FPointerEvent& MouseEvent)
{
// Call parent implementation
SWidget::OnMouseLeave(MouseEvent);
OnUnhovered.ExecuteIfBound();
Invalidate(EInvalidateWidget::Layout);
}
STobiiGazeFocusableWidget* STobiiGazeFocusableWidget::TryCastToTobiiGazeFocusable(SWidget* Widget)
{
if (Widget != nullptr && Widget->GetType() == FName("STobiiGazeFocusableWidget"))
{
return (STobiiGazeFocusableWidget*)Widget;
}
return nullptr;
}

View File

@@ -0,0 +1,318 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "STobiiRadialMenuWidget.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "Engine/Canvas.h"
#include "Engine/Engine.h"
#include "Components/WidgetComponent.h"
#include "Framework/Application/SlateApplication.h"
#include "Slate/SceneViewport.h"
#define TWO_PI (6.28318530718)
#define PI_OVER_TWO (1.57079632679)
STobiiRadialMenuWidget::STobiiRadialMenuWidget()
: Children(this)
{
SetCanTick(false);
bCanSupportFocus = false;
}
void STobiiRadialMenuWidget::Construct(const STobiiRadialMenuWidget::FArguments& InArgs)
{
const int32 NumSlots = InArgs.Slots.Num();
for (int32 SlotIndex = 0; SlotIndex < NumSlots; ++SlotIndex)
{
Children.Add(InArgs.Slots[SlotIndex]);
}
bUseHardBorder = InArgs._UseHardBorder;
BorderColor = InArgs._BorderColor;
PanelColor = InArgs._PanelColor;
BorderThicknessPx = InArgs._BorderThicknessPx;
SegmentSeparationPx = InArgs._SegmentSeparationPx;
AngularDisplacementDeg = InArgs._AngularDisplacementDeg;
RadiusPx = InArgs._RadiusPx;
VertexCount = InArgs._VertexCount;
BrushResourceHandle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(*FCoreStyle::Get().GetDefaultBrush());
}
void STobiiRadialMenuWidget::ClearChildren()
{
if (Children.Num())
{
Invalidate(EInvalidateWidget::Layout);
Children.Empty();
}
}
int32 STobiiRadialMenuWidget::RemoveSlot(const TSharedRef<SWidget>& SlotWidget)
{
Invalidate(EInvalidateWidget::Layout);
for (int32 SlotIdx = 0; SlotIdx < Children.Num(); ++SlotIdx)
{
if (SlotWidget == Children[SlotIdx].GetWidget())
{
Children.RemoveAt(SlotIdx);
return SlotIdx;
}
}
return -1;
}
void STobiiRadialMenuWidget::GeneratePanelRenderData(const FGeometry& AllottedGeometry, int32 NrChildren, TArray<FTobiiRadialMenuPanelRenderData>& OutChildRenderData) const
{
OutChildRenderData.Empty(NrChildren);
const FVector2D WidgetCenter = AllottedGeometry.GetLocalSize() / 2.0f;
const int32 VerticesPerSegment = FMath::Max(VertexCount / Children.Num(), 3);
const float ActualRadius = FMath::Min(WidgetCenter.X, WidgetCenter.Y);
const float AngleStep = TWO_PI / Children.Num();
const float AngleHalfStep = AngleStep / 2.0f;
const float AngleMinorStep = AngleStep / VerticesPerSegment;
const float HalfSeparation = SegmentSeparationPx / 2.0f;
const float BorderCenterOffsetDist = HalfSeparation / FMath::Sin(AngleHalfStep);
const float BorderSeparationAngle = FMath::Asin(HalfSeparation / ActualRadius);
const float NoBorderDistOffset = HalfSeparation + BorderThicknessPx;
const float NoBorderRadius = ActualRadius - BorderThicknessPx;
const float NoBorderCenterOffsetDist = NoBorderDistOffset / FMath::Sin(AngleHalfStep);
const float NoBorderSeparationAngle = FMath::Asin(NoBorderDistOffset / NoBorderRadius);
float CurrentChildAngle = PI_OVER_TWO + FMath::DegreesToRadians(AngularDisplacementDeg); //Start at the top
for (int32 ChildIndex = 0; ChildIndex < NrChildren; ++ChildIndex)
{
FTobiiRadialMenuPanelRenderData NewRenderData;
const STobiiRadialMenuWidget::FSlot& CurChild = Children[ChildIndex];
const float ChildAlpha = CurChild.AlphaAttr.Get();
FColor AdjustedBorderColor = BorderColor;
AdjustedBorderColor.A *= ChildAlpha;
FColor AdjustedPanelColor = PanelColor;
AdjustedPanelColor.A *= ChildAlpha;
//Panel polygon
{
float ChildSine, ChildCosine;
FMath::SinCos(&ChildSine, &ChildCosine, -CurrentChildAngle);
FVector2D WidgetCenterToChildCenterDir(ChildCosine, ChildSine);
//First generate the nexus point
const FVector2D CenterVertexPosition = WidgetCenter + WidgetCenterToChildCenterDir * NoBorderCenterOffsetDist;
FSlateVertex CenterVertex = FSlateVertex::Make<ESlateVertexRounding::Enabled>(AllottedGeometry.GetAccumulatedRenderTransform(), CenterVertexPosition, FVector2D::ZeroVector, AdjustedPanelColor);
NewRenderData.PanelVertexData.Add(CenterVertex);
//Next we need to find our segment extreme points
const float SegmentStartVertexAngle = CurrentChildAngle - AngleHalfStep + NoBorderSeparationAngle;
const float SegmentEndVertexAngle = CurrentChildAngle + AngleHalfStep - NoBorderSeparationAngle;
//Generate all edge vertices
for (int32 VertexIdx = 0; VertexIdx < VerticesPerSegment; VertexIdx++)
{
const float AngleAlpha = (float)VertexIdx / (VerticesPerSegment - 1);
const float CurrentAngle = FMath::Lerp(SegmentStartVertexAngle, SegmentEndVertexAngle, AngleAlpha);
float VertexSine, VertexCosine;
FMath::SinCos(&VertexSine, &VertexCosine, -CurrentAngle);
const FVector2D SegmentVertexPosition = WidgetCenter + FVector2D(NoBorderRadius * VertexCosine, NoBorderRadius * VertexSine);
FSlateVertex SegmentVertex = FSlateVertex::Make<ESlateVertexRounding::Enabled>(AllottedGeometry.GetAccumulatedRenderTransform(), SegmentVertexPosition, FVector2D::ZeroVector, AdjustedPanelColor);
NewRenderData.PanelVertexData.Add(SegmentVertex);
}
//Build indices
for (int32 VertexIdx = 1; VertexIdx < VerticesPerSegment; VertexIdx++)
{
NewRenderData.PanelIndexData.Add(0);
NewRenderData.PanelIndexData.Add(VertexIdx);
NewRenderData.PanelIndexData.Add(VertexIdx + 1);
}
}
//Border polygon
{
float ChildSine, ChildCosine;
FMath::SinCos(&ChildSine, &ChildCosine, -CurrentChildAngle);
FVector2D WidgetCenterToChildCenterDir(ChildCosine, ChildSine);
//First generate the nexus point
const FVector2D CenterVertexPosition = WidgetCenter + WidgetCenterToChildCenterDir * BorderCenterOffsetDist;
FSlateVertex CenterVertex = FSlateVertex::Make<ESlateVertexRounding::Enabled>(AllottedGeometry.GetAccumulatedRenderTransform(), CenterVertexPosition, FVector2D::ZeroVector, AdjustedBorderColor);
NewRenderData.BorderVertexData.Add(CenterVertex);
//Next we need to find our segment extreme points
const float SegmentStartVertexAngle = CurrentChildAngle - AngleHalfStep + BorderSeparationAngle;
const float SegmentEndVertexAngle = CurrentChildAngle + AngleHalfStep - BorderSeparationAngle;
//Generate all edge vertices
for (int32 VertexIdx = 0; VertexIdx < VerticesPerSegment; VertexIdx++)
{
const float AngleAlpha = (float)VertexIdx / (VerticesPerSegment - 1);
const float CurrentAngle = FMath::Lerp(SegmentStartVertexAngle, SegmentEndVertexAngle, AngleAlpha);
float VertexSine, VertexCosine;
FMath::SinCos(&VertexSine, &VertexCosine, -CurrentAngle);
const FVector2D SegmentVertexPosition = WidgetCenter + FVector2D(ActualRadius * VertexCosine, ActualRadius * VertexSine);
FSlateVertex SegmentVertex = FSlateVertex::Make<ESlateVertexRounding::Enabled>(AllottedGeometry.GetAccumulatedRenderTransform(), SegmentVertexPosition, FVector2D::ZeroVector, AdjustedBorderColor);
NewRenderData.BorderVertexData.Add(SegmentVertex);
}
//Add the panel vertices at the end of the array
const int32 FirstPanelVertexIdx = NewRenderData.BorderVertexData.Num();
for (FSlateVertex SlateVertex : NewRenderData.PanelVertexData)
{
if (bUseHardBorder)
{
SlateVertex.Color = AdjustedBorderColor;
}
NewRenderData.BorderVertexData.Add(SlateVertex);
}
//Build indices, building quads from the inner panel outwards
for (int32 VertexIdx = 0; VertexIdx < VerticesPerSegment; VertexIdx++)
{
//First tri
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx + VertexIdx);
NewRenderData.BorderIndexData.Add(VertexIdx);
NewRenderData.BorderIndexData.Add(VertexIdx + 1);
//Second tri
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx + VertexIdx);
NewRenderData.BorderIndexData.Add(VertexIdx + 1);
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx + VertexIdx + 1);
}
//Final two tris
NewRenderData.BorderIndexData.Add(NewRenderData.BorderVertexData.Num() - 1);
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx - 1);
NewRenderData.BorderIndexData.Add(0);
NewRenderData.BorderIndexData.Add(NewRenderData.BorderVertexData.Num() - 1);
NewRenderData.BorderIndexData.Add(0);
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx);
}
OutChildRenderData.Add(MoveTemp(NewRenderData));
CurrentChildAngle += AngleStep;
}
}
void STobiiRadialMenuWidget::OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const
{
if (Children.Num() > 0)
{
const FVector2D WidgetCenter = AllottedGeometry.GetLocalSize() / 2.0f;
const float ActualRadius = FMath::Min(WidgetCenter.X, WidgetCenter.Y);
const float AngleStep = TWO_PI / Children.Num();
FVector2D LargestInscribedRect;
float DistanceToRectCenter;
UTobiiGTOMBlueprintLibrary::FindLargestInscribedAlignedRect(AngleStep, ActualRadius, LargestInscribedRect, DistanceToRectCenter);
const float ChildElementSideLength = FMath::Min(LargestInscribedRect.X, LargestInscribedRect.Y);
const float SlightlyScaledDownSize = ChildElementSideLength * 0.8f;
float CurrentAngle = PI_OVER_TWO + FMath::DegreesToRadians(AngularDisplacementDeg); //Start at the top
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
const STobiiRadialMenuWidget::FSlot& CurChild = Children[ChildIndex];
const TSharedRef<SWidget>& CurWidget = CurChild.GetWidget();
const EVisibility ChildVisibility = CurWidget->GetVisibility();
if (ArrangedChildren.Accepts(ChildVisibility))
{
const FVector2D Offset = CurChild.OffsetAttr.Get();
float ChildSine, ChildCosine;
FMath::SinCos(&ChildSine, &ChildCosine, -CurrentAngle);
const FVector2D ChildCenter(DistanceToRectCenter * ChildCosine, DistanceToRectCenter * ChildSine);
const float ChildScale = CurChild.ScaleAttr.Get();
const FVector2D ChildSize(SlightlyScaledDownSize * ChildScale, SlightlyScaledDownSize * ChildScale);
const FVector2D BaseChildOffset = -ChildSize / 2.0f;
const FVector2D LocalPosition = WidgetCenter + ChildCenter + BaseChildOffset + Offset;
CurrentAngle += AngleStep;
ArrangedChildren.AddWidget(ChildVisibility, AllottedGeometry.MakeChild(CurWidget, LocalPosition, ChildSize));
}
}
}
}
int32 STobiiRadialMenuWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
SCOPED_NAMED_EVENT_TEXT("SConstraintCanvas", FColor::Orange);
if (Children.Num() < 2)
{
return LayerId;
}
FArrangedChildren ArrangedChildren(EVisibility::Visible);
OnArrangeChildren(AllottedGeometry, ArrangedChildren);
TArray<FTobiiRadialMenuPanelRenderData> ChildRenderData;
GeneratePanelRenderData(AllottedGeometry, Children.Num(), ChildRenderData);
const bool bForwardedEnabled = ShouldBeEnabled(bParentEnabled);
const float AngleStep = TWO_PI / Children.Num();
float CurrentChildAngle = PI_OVER_TWO + FMath::DegreesToRadians(AngularDisplacementDeg); //Start at the top
// Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents
// wants to an overlay for all of its contents.
int32 MaxLayerId = LayerId;
const FPaintArgs NewArgs = Args.WithNewParent(this);
for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex)
{
FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex];
if (!IsChildWidgetCulled(MyCullingRect, CurWidget))
{
MaxLayerId++;
//Draw underlying panel if our render data is valid
if (ChildIndex < ChildRenderData.Num())
{
const FTobiiRadialMenuPanelRenderData& CurrentRenderData = ChildRenderData[ChildIndex];
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId
, BrushResourceHandle
, CurrentRenderData.PanelVertexData
, CurrentRenderData.PanelIndexData
, nullptr, 0, 0, ESlateDrawEffect::NoPixelSnapping);
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId
, BrushResourceHandle
, CurrentRenderData.BorderVertexData
, CurrentRenderData.BorderIndexData
, nullptr, 0, 0, ESlateDrawEffect::NoPixelSnapping);
}
const STobiiRadialMenuWidget::FSlot& CurChild = Children[ChildIndex];
FWidgetStyle CompoundedWidgetStyle = FWidgetStyle(InWidgetStyle).BlendColorAndOpacityTint(FLinearColor(1.0f, 1.0f, 1.0f, CurChild.AlphaAttr.Get()));
CurWidget.Widget->Paint(NewArgs, CurWidget.Geometry, MyCullingRect, OutDrawElements, MaxLayerId, CompoundedWidgetStyle, bForwardedEnabled);
CurrentChildAngle += AngleStep;
}
}
return MaxLayerId;
}
FVector2D STobiiRadialMenuWidget::ComputeDesiredSize(float LayoutScaleMultiplier) const
{
const float DiameterPx = RadiusPx * 2.0f;
return FVector2D(DiameterPx, DiameterPx);
}
FChildren* STobiiRadialMenuWidget::GetChildren()
{
return &Children;
}

View File

@@ -0,0 +1,223 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiGazeFocusableWidget.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "Components/SizeBoxSlot.h"
#define LOCTEXT_NAMESPACE "UMG"
static TMap<FTobiiFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableWidget>> GRegisteredTobiiScreenSpaceFocusableWidgets;
UTobiiGazeFocusableWidget::UTobiiGazeFocusableWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, OnHovered()
, OnUnhovered()
, bHasGazeFocus(false)
, bIsGazeFocusable(true)
, GazeFocusPriority(0.0f)
, MaxFocusDistance(-1.0f)
, FocusLayer("")
, ReceivedGazeFocus()
, LostGazeFocus()
, CleanUIMode(ETobiiCleanUIMode::Disabled)
, TimeCleanUIIsSuppressedForSecs(0.0f)
, CleanUIFadeInTimeSecsOverride(-1.0f)
, CleanUIFadeOutTimeSecsOverride(-1.0f)
, CleanUIMinAlphaOverride(-1.0f)
, CleanUIMaxAlphaOverride(-1.0f)
, bTriggerCleanUIOnMouseOver(false)
, WorldSpaceHostWidgetComponent()
, MySlateWidget()
{
Visibility = ESlateVisibility::Visible;
bIsVariable = true;
}
void UTobiiGazeFocusableWidget::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
MySlateWidget.Reset();
}
TSharedRef<SWidget> UTobiiGazeFocusableWidget::RebuildWidget()
{
MySizeBox = MySlateWidget = SNew(STobiiGazeFocusableWidget)
.OnHovered_UObject(this, &ThisClass::SlateHandleHovered)
.OnUnhovered_UObject(this, &ThisClass::SlateHandleUnhovered);
MySlateWidget->UMGWidget = this;
if (GetChildrenCount() > 0)
{
Cast<USizeBoxSlot>(GetContentSlot())->BuildSlot(MySizeBox.ToSharedRef());
}
return MySizeBox.ToSharedRef();
}
bool UTobiiGazeFocusableWidget::IsHoveredByPointer()
{
if (MySlateWidget.IsValid())
{
return MySlateWidget->IsHoveredByPointer();
}
return false;
}
void UTobiiGazeFocusableWidget::RegisterWidgetToGTOM(UWidgetComponent* HostWidget)
{
if (HostWidget != nullptr)
{
WorldSpaceHostWidgetComponent = HostWidget;
}
else
{
uint32 Uid = GetUniqueID();
if (GRegisteredTobiiScreenSpaceFocusableWidgets.Contains(Uid))
{
//Overwrite
GRegisteredTobiiScreenSpaceFocusableWidgets[Uid] = this;
}
else
{
GRegisteredTobiiScreenSpaceFocusableWidgets.Add(Uid, this);
}
}
}
bool UTobiiGazeFocusableWidget::IsWorldSpaceWidget()
{
return WorldSpaceHostWidgetComponent.IsValid();
}
UWidgetComponent* UTobiiGazeFocusableWidget::GetWorldSpaceHostWidgetComponent()
{
return WorldSpaceHostWidgetComponent.IsValid() ? WorldSpaceHostWidgetComponent.Get() : nullptr;
}
bool UTobiiGazeFocusableWidget::HasFocus()
{
FTobiiGazeFocusData FocusData;
return UTobiiGTOMBlueprintLibrary::GetGazeFocusData(FocusData) && FocusData.FocusedWidget == this;
}
bool UTobiiGazeFocusableWidget::IsInFocusCollection()
{
TArray<FTobiiGazeFocusData> AllFocusData;
if (UTobiiGTOMBlueprintLibrary::GetAllGazeFocusData(AllFocusData))
{
for (const FTobiiGazeFocusData& FocusData : AllFocusData)
{
if (FocusData.FocusedWidget == this)
{
return true;
}
}
}
return false;
}
float UTobiiGazeFocusableWidget::GetCleanUIAlpha()
{
if (MySlateWidget.IsValid())
{
return MySlateWidget->GetCleanUIAlpha();
}
return 1.0f;
}
void UTobiiGazeFocusableWidget::AddGazeFocusableWidgetToPollHitsFrom(UTobiiGazeFocusableWidget* GazeFocusableWidgetToPoll, bool PollGazeHits /*= true*/, bool PollMouseHits /*= true*/)
{
if (GazeFocusableWidgetToPoll != nullptr)
{
STobiiGazeFocusableWidget* SlateCleanUIContainerToPoll = GazeFocusableWidgetToPoll->GetSlateGazeFocusableWidget();
if (SlateCleanUIContainerToPoll != nullptr && MySlateWidget.IsValid())
{
MySlateWidget->AddExtraCleanUIContainerToPollHitsFrom(SlateCleanUIContainerToPoll, PollGazeHits, PollMouseHits);
}
}
}
void UTobiiGazeFocusableWidget::RemoveGazeFocusableWidgetToPollHitsFrom(UTobiiGazeFocusableWidget* GazeFocusableWidgetToStopPolling)
{
if (GazeFocusableWidgetToStopPolling != nullptr)
{
STobiiGazeFocusableWidget* SlateCleanUIContainerToStopPolling = GazeFocusableWidgetToStopPolling->GetSlateGazeFocusableWidget();
if (SlateCleanUIContainerToStopPolling != nullptr && MySlateWidget.IsValid())
{
MySlateWidget->RemoveExtraCleanUIContainerToPollHitsFrom(SlateCleanUIContainerToStopPolling);
}
}
}
void UTobiiGazeFocusableWidget::ReceiveFocus()
{
ReceivedGazeFocus.Broadcast(this);
}
void UTobiiGazeFocusableWidget::LoseFocus()
{
LostGazeFocus.Broadcast(this);
}
void UTobiiGazeFocusableWidget::SlateHandleHovered()
{
OnHovered.Broadcast(this);
}
void UTobiiGazeFocusableWidget::SlateHandleUnhovered()
{
OnUnhovered.Broadcast(this);
}
STobiiGazeFocusableWidget* UTobiiGazeFocusableWidget::GetSlateGazeFocusableWidget()
{
if (MySlateWidget.IsValid())
{
return MySlateWidget.Get();
}
return nullptr;
}
TMap<FTobiiFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableWidget>>& UTobiiGazeFocusableWidget::GetTobiiScreenSpaceFocusableWidgets()
{
TArray<FTobiiFocusableUID> IdsToPrune;
for (auto& WidgetPair : GRegisteredTobiiScreenSpaceFocusableWidgets)
{
if (!WidgetPair.Value.IsValid())
{
IdsToPrune.Add(WidgetPair.Key);
}
}
for (FTobiiFocusableUID IdToPrune : IdsToPrune)
{
GRegisteredTobiiScreenSpaceFocusableWidgets.Remove(IdToPrune);
}
return GRegisteredTobiiScreenSpaceFocusableWidgets;
}
#if WITH_EDITOR
const FText UTobiiGazeFocusableWidget::GetPaletteCategory()
{
return LOCTEXT("Tobii", "Tobii");
}
#endif
///////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,334 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiRadialMenuWidget.h"
#include "TobiiGazeFocusableWidget.h"
#include "STobiiGazeFocusableWidget.h"
#define LOCTEXT_NAMESPACE "UMG"
UTobiiRadialMenuSlot::UTobiiRadialMenuSlot(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, Offset(0.0f, 0.0f)
, Scale(1.0f)
, Alpha(1.0f)
, Slot(nullptr)
{
}
void UTobiiRadialMenuSlot::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
Slot = nullptr;
}
void UTobiiRadialMenuSlot::BuildSlot(TSharedRef<STobiiRadialMenuWidget> RadialMenu)
{
Slot = &RadialMenu->AddSlot()
[
Content == nullptr ? SNullWidget::NullWidget : Content->TakeWidget()
];
SynchronizeProperties();
}
#if WITH_EDITOR
bool UTobiiRadialMenuSlot::NudgeByDesigner(const FVector2D& NudgeDirection, const TOptional<int32>& GridSnapSize)
{
return false;
}
bool UTobiiRadialMenuSlot::DragDropPreviewByDesigner(const FVector2D& LocalCursorPosition, const TOptional<int32>& XGridSnapSize, const TOptional<int32>& YGridSnapSize)
{
return false;
}
void UTobiiRadialMenuSlot::SynchronizeFromTemplate(const UPanelSlot* const TemplateSlot)
{
}
#endif //WITH_EDITOR
void UTobiiRadialMenuSlot::SetOffset(FVector2D InOffset)
{
Offset = InOffset;
if (Slot)
{
Slot->Offset(InOffset);
}
}
FVector2D UTobiiRadialMenuSlot::GetOffset() const
{
if (Slot)
{
return Slot->OffsetAttr.Get();
}
return FVector2D::ZeroVector;
}
void UTobiiRadialMenuSlot::SetScale(float InScale)
{
Scale = InScale;
if (Slot)
{
Slot->Scale(InScale);
}
}
float UTobiiRadialMenuSlot::GetScale() const
{
if (Slot)
{
return Slot->ScaleAttr.Get();
}
return 1.0f;
}
void UTobiiRadialMenuSlot::SetAlpha(float InAlpha)
{
Alpha = InAlpha;
if (Slot)
{
Slot->Alpha(InAlpha);
}
}
float UTobiiRadialMenuSlot::GetAlpha() const
{
if (Slot)
{
return Slot->AlphaAttr.Get();
}
return 1.0f;
}
void UTobiiRadialMenuSlot::SynchronizeProperties()
{
SetOffset(Offset);
SetScale(Scale);
SetAlpha(Alpha);
}
#if WITH_EDITOR
void UTobiiRadialMenuSlot::PreEditChange(class FEditPropertyChain& PropertyAboutToChange)
{
Super::PreEditChange(PropertyAboutToChange);
}
void UTobiiRadialMenuSlot::PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent)
{
SynchronizeProperties();
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////
UTobiiRadialMenuWidget::UTobiiRadialMenuWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bUseHardBorder(true)
, BorderColor(FColor(230, 230, 230))
, PanelColor(FColor(70, 70, 70))
, BorderThicknessPx(5.0f)
, SegmentSeparationPx(10.0f)
, AngularDisplacementDeg(0.0f)
, RadiusPx(500.0f)
, VertexCount(360)
{
bIsVariable = false;
STobiiRadialMenuWidget::FArguments Defaults;
Visibility = UWidget::ConvertRuntimeToSerializedVisibility(Defaults._Visibility.Get());
}
void UTobiiRadialMenuWidget::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
MyRadialMenu.Reset();
}
TSharedRef<SWidget> UTobiiRadialMenuWidget::RebuildWidget()
{
MyRadialMenu = SNew(STobiiRadialMenuWidget)
.UseHardBorder(bUseHardBorder)
.BorderColor(BorderColor)
.PanelColor(PanelColor)
.BorderThicknessPx(BorderThicknessPx)
.SegmentSeparationPx(SegmentSeparationPx)
.AngularDisplacementDeg(AngularDisplacementDeg)
.RadiusPx(RadiusPx)
.VertexCount(VertexCount);
for (UPanelSlot* PanelSlot : Slots)
{
if (UTobiiRadialMenuSlot* TypedSlot = Cast<UTobiiRadialMenuSlot>(PanelSlot))
{
TypedSlot->Parent = this;
TypedSlot->BuildSlot(MyRadialMenu.ToSharedRef());
UTobiiGazeFocusableWidget* GazeFocusableWidget = Cast<UTobiiGazeFocusableWidget>(TypedSlot->Content);
if (GazeFocusableWidget != nullptr)
{
STobiiGazeFocusableWidget* SlateWidget = GazeFocusableWidget->GetSlateGazeFocusableWidget();
//SlateWidget->ParentRadialMenuSlot = TypedSlot->GetSlateSlot();
}
}
}
return MyRadialMenu.ToSharedRef();
}
UClass* UTobiiRadialMenuWidget::GetSlotClass() const
{
return UTobiiRadialMenuSlot::StaticClass();
}
void UTobiiRadialMenuWidget::SetUseHardBorder(bool bNewUseHardBorder)
{
bUseHardBorder = bNewUseHardBorder;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->bUseHardBorder = bUseHardBorder;
}
}
void UTobiiRadialMenuWidget::SetBorderColor(FColor NewBorderColor)
{
BorderColor = NewBorderColor;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->BorderColor = BorderColor;
}
}
void UTobiiRadialMenuWidget::SetPanelColor(FColor NewPanelColor)
{
PanelColor = NewPanelColor;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->PanelColor = PanelColor;
}
}
void UTobiiRadialMenuWidget::SetBorderThicknessPx(float NewBorderThicknessPx)
{
BorderThicknessPx = NewBorderThicknessPx;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->BorderThicknessPx = BorderThicknessPx;
}
}
void UTobiiRadialMenuWidget::SetSegmentSeparationPx(float NewSegmentSeparationPx)
{
SegmentSeparationPx = NewSegmentSeparationPx;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->SegmentSeparationPx = SegmentSeparationPx;
}
}
void UTobiiRadialMenuWidget::SetAngularDisplacementDeg(float NewAngularDisplacementDeg)
{
AngularDisplacementDeg = NewAngularDisplacementDeg;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->AngularDisplacementDeg = AngularDisplacementDeg;
}
}
void UTobiiRadialMenuWidget::SetRadiusPx(float NewRadiusPx)
{
RadiusPx = NewRadiusPx;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->RadiusPx = RadiusPx;
}
}
void UTobiiRadialMenuWidget::SetVertexCount(int32 NewVertexCount)
{
VertexCount = NewVertexCount;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->VertexCount = VertexCount;
}
}
void UTobiiRadialMenuWidget::OnSlotAdded(UPanelSlot* InSlot)
{
// Add the child to the live canvas if it already exists
if (MyRadialMenu.IsValid())
{
CastChecked<UTobiiRadialMenuSlot>(InSlot)->BuildSlot(MyRadialMenu.ToSharedRef());
}
}
void UTobiiRadialMenuWidget::OnSlotRemoved(UPanelSlot* InSlot)
{
// Remove the widget from the live slot if it exists.
if (MyRadialMenu.IsValid())
{
TSharedPtr<SWidget> Widget = InSlot->Content->GetCachedWidget();
if (Widget.IsValid())
{
MyRadialMenu->RemoveSlot(Widget.ToSharedRef());
}
}
}
UTobiiRadialMenuSlot* UTobiiRadialMenuWidget::AddRadialMenuChild(UWidget* Content)
{
return Cast<UTobiiRadialMenuSlot>(Super::AddChild(Content));
}
bool UTobiiRadialMenuWidget::GetGeometryForSlot(int32 SlotIndex, FGeometry& ArrangedGeometry) const
{
UTobiiRadialMenuSlot* PanelSlot = CastChecked<UTobiiRadialMenuSlot>(Slots[SlotIndex]);
return GetGeometryForSlot(PanelSlot, ArrangedGeometry);
}
bool UTobiiRadialMenuWidget::GetGeometryForSlot(UTobiiRadialMenuSlot* InSlot, FGeometry& ArrangedGeometry) const
{
if (InSlot->Content == nullptr)
{
return false;
}
if (MyRadialMenu.IsValid())
{
FArrangedChildren ArrangedChildren(EVisibility::All);
MyRadialMenu->ArrangeChildren(MyRadialMenu->GetCachedGeometry(), ArrangedChildren);
for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ChildIndex++)
{
if (ArrangedChildren[ChildIndex].Widget == InSlot->Content->GetCachedWidget())
{
ArrangedGeometry = ArrangedChildren[ChildIndex].Geometry;
return true;
}
}
}
return false;
}
#if WITH_EDITOR
const FText UTobiiRadialMenuWidget::GetPaletteCategory()
{
return LOCTEXT("Tobii", "Tobii");
}
#endif
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE